Javascript中的string型別使用UTF-16編碼
在JavaScript中,所有的string型別(或者被稱為DOMString)都是使用UTF-16編碼的。
MDN
DOMString
是一個UTF-16字串。由於JavaScript已經使用了這樣的字串,所以DOMString 直接對映到 一個String
。
將 null
傳遞給接受DOMString的方法或引數時通常會把其stringifies為“null”。
引出的Unicode中UTF-8與UTF-16編碼的詳細瞭解
Unicode編碼
Unicode(統一碼、萬國碼、單一碼)是電腦科學領域裡的一項業界標準,包括字符集、編碼方案等。Unicode 是為了解決傳統的字元編碼方案的侷限而產生的,它為每種語言中的每個字元設定了統一併且唯一的
通常Unicode編碼是通過2 Byte來表示一個字元的,如U+A12B
,2 Byte的二進位制表示方法結果就是1010(A)0001(1) 0010(2)1011(B)
。
需要注意的是:UTF是Unicode TransferFormat的縮寫,UTF-8和UTF-16都是把Unicode碼轉換成程式資料的一種編碼方式。
UTF-8
UTF-8(8-bit Unicode Transformation Format)是一種針對Unicode的可變長度字元編碼,又稱萬國碼。由Ken Thompson於1992年建立。現在已經標準化為RFC 3629。UTF-8用1到6個位元組編碼Unicode字元(儘管如此,2003年11月UTF-8被RFC 3629重新規範,只能使用原來Unicode定義的區域,U+0000到U+10FFFF,也就是說最多4個位元組)。用在網頁上可以統一頁面顯示中文簡體繁體及其它語言(如英文,日文,韓文)。
UTF-8的來歷
UTF-8
的規範裡充斥著這樣神祕的句子:“第一個位元組由110
開始,接著的位元組由10
開始”,“第一個位元組由1110
開始,接著的位元組由10
開始”。
那麼這到底是什麼意思呢?為什麼要這麼做呢?
我們先從二進位制說起。我們都知道,一個位元組是由8
個二進位制位構成的,最小就是0000 0000
,最大就是1111 1111
。那麼一個位元組所能表示的最多字元數就是2
的8
次方,也就是256
。對於26
個英文字母來說,大小寫全算上就是52
個,再加上10
個阿拉伯數字,62
個字元,用可以表達256
個不同字元的一個位元組來儲存是足夠了。
但是,我們中國的常用漢字就有3000
多個,用一個只能表達256
個字元的位元組顯然是不夠儲存的。至少也需要有2
1
個位元組是8
個二進位制位,2
個位元組就是16
個二進位制位,最多可以表達2
的16
次方,也就是256*256=65536
。65536
個字元足夠容納所有中國的漢字,外帶日語、韓語、阿拉伯語、稀其古怪語等等各種各樣的字元。所以這樣就產生了Unicode
,因為它用2
位元組表示字元,所以更嚴格來講應該叫UCS-2
,後來因為怪字元太多,2
位元組都不夠用了,所以又搞出來了一個4
位元組表示的方法,稱作UCS-4
。不過現在對絕大多數人來講UCS-2
已經是足夠了。
Unicode
本來是一個好東西,用2
位元組表示65536
種字元,全人類皆大歡喜的事情。但是偏偏有一幫子西洋人,非要認為這個東西是一種浪費,說我們英文就最多隻需要26
個字母就夠了,1
個位元組就夠了,為什麼要浪費2
位元組呢?比如說字母A
就是0100 0001
,這一個位元組就夠了的東西,你弄2
位元組,非要在前面加8
個0
,0000 0000 0100 0001
,這不是浪費嗎?我們就偏要用1
位元組表示英文。
好吧,我們全人類只好做妥協,規定每個位元組,只要看見0
打頭的,就知道這是英文字母,這肯定不是漢字,只有看見1
開頭的,才認為這是漢字。
但是我們漢字用1
個位元組表示不下,那好辦,用2
個1
開頭的字元表示1
個漢字。這樣本來16
個二進位制位,減去2
個開頭的1
,只剩下14
個二進位制位了,2
的14
次方就是16384
個字元,對於中文來講,也是足夠用了。但是無奈他們還是想表達65536
種字元,那怎麼辦呢?就需要3
個位元組才能容納得下了,於是UTF-8
粉墨登場。
首先,首位為0
的字元被佔了,只要遇到0
開頭的字元,就知道這是一個1
位元組的字元,不必再往後數了,直接拿來用就可以,最多表示128
種字元,從0000 0000
到0111 1111
,也就是從0
到127
。
接下來的事情就比較蹊蹺了。我們怎麼用1
開頭的字元既表示2
位元組,又表示3
位元組呢?假設我們只判斷首位的1
,這顯然是不行的,沒有辦法區分,所以我們可以用10
或者11
開頭的字元來表示2
位元組,但是3
位元組又該以什麼開頭?或者可以用10
開頭表示2
位元組,用11
開頭表示3
位元組?那麼4
位元組的字元將來又該怎麼辦?也許我們可以用110
開頭表示3
位元組,用111
開頭表示4
位元組?那麼5
位元組6
位元組呢?似乎我們看到了一個規律:前面的1
越多,代表位元組數越多。
這時候,看一下我們的第一種方案:用10
開頭表示2
位元組,那麼我們的一個字元將是
10xx xxxx 10xx xxxx
用110
表示3
位元組,那麼一個3
位元組的字元將是:
110x xxxx 110x xxxx 110x xxxx
這樣無疑是能區分得開的。但是4
位元組怎麼辦?
1110 xxxx 1110 xxxx 1110 xxxx 1110 xxxx
嗎?這樣也能區分開,但似乎有點浪費。因為每個位元組的前半扇都被無用的位佔滿了,真正有意義的只有後面一半。
或者我們乾脆這樣做得了,我們來設計方案二:為了節省起見,所有後面的字元,我們統統都以10
開頭,只要遇見10
我們就知道它只是整個字元流的一部分,它肯定不是開頭,但是10
這個開頭已經被我們剛剛方案一的2
位元組字元佔用了,怎麼辦?好辦,把2
位元組字元的開頭從10
改成110
,這樣它就肯定不會和10
衝突了。於是2
位元組字元變成
110x xxxx 10xx xxxx
再往後順推,3
位元組字元變成
1110 xxxx 10xx xxxx 10xx xxxx
4
位元組字元變成
1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
好像比剛才的方案一有所節省呢!並且還帶來了額外的好處:如果我沒有見到前面的110
或者1110
開頭的位元組,而直接見到了10
開頭的位元組,毫無疑問地可以肯定我遇到的不是一個完整字元的開頭,我可以直接忽略這個錯誤的位元組,而直接找下一個正確字元的開頭。
這個改良之後的方案二就是UTF-8
!
UTF-8表示的字元數
現在,我們來算一下在UTF-8
方案裡,每一種位元組可以表示多少種字元。
1
位元組的字元,以0
開頭的,0xxx xxxx
,後面7
個有效位,2
的7
次方,最多可以表示128
種字元。
2
位元組的字元,110x xxxx 10xx xxxx
,數一數,11
個x
,所以是2
的11
次方,2
的10
次方是1024
,11
次方就是2048
,很不幸,只能表示2048
種字元,而我們的常用漢字就有3000
多個,看來在這一區是放不下了,只好挪到3
位元組。
3
位元組的字元,1110 xxxx 10xx xxxx 10xx xxxx
,數一數,16
個x
,2
的16
次方,最多可以表示65536
個字元,所以我們的漢字就放在這一區,所以在UTF-8
方案裡我們的漢字都是以3
個位元組表示的。
所以這也就是這一張表的來歷:
UTF-16
UTF-16是Unicode字元編碼五層次模型的第三層:字元編碼表(Character Encoding Form,也稱為 "storage format")的一種實現方式。即把Unicode字符集的抽象碼位對映為16位長的整數(即碼元, 長度為2 Byte)的序列,用於資料儲存或傳遞。Unicode字元的碼位,需要1個或者2個16位長的碼元來表示,因此這是一個變長表示。
起初,UTF-16任何字元對應的數字都用兩個位元組來保。但是,65536顯然是不算太多的數字,用它來表示常用的字元是沒一點問題,足夠了。但如果加上很多特殊的就也不夠了。於是,從1996年開始又來了第二個版本,用四個位元組表示所有字元。這樣就出現了UTF-8,UTF16,UTF-32。UTF-32就是把所有的字元都用32bit也就是4個位元組來表示。然後UTF-8,UTF-16就視情況而定了,UTF-16可以選擇兩位元組或四位元組。
UTF-16 並不是一個完美的選擇,它存在幾個方面的問題:
- UTF-16 能表示的字元數有 6 萬多,看起來很多,但是實際上目前 Unicode 5.0 收錄的字元已經達到 99024 個字元,早已超過 UTF-16 的儲存範圍;這直接導致 UTF-16 地位頗為尷尬——如果誰還在想著只要使用 UTF-16 就可以高枕無憂的話,恐怕要失望了
- UTF-16 存在大小端位元組序問題,這個問題在進行資訊交換時特別突出——如果位元組序未協商好,將導致亂碼;如果協商好,但是雙方一個採用大端一個採用小端,則必然有一方要進行大小端轉換,效能損失不可避免(大小端問題其實不像看起來那麼簡單,有時會涉及硬體、作業系統、上層軟體多個層次,可能會進行多次轉換)
- 另外,容錯性低有時候也是一大問題——區域性的位元組錯誤,特別是丟失或增加可能導致所有後續字元全部錯亂,錯亂後要想恢復,可能很簡單,也可能會非常困難。(這一點在日常生活裡大家感覺似乎無關緊要,但是在很多特殊環境下卻是巨大的缺陷)
目前支撐我們繼續使用 UTF-16 的理由主要是考慮到它是雙位元組的,在計算字串長度、執行索引操作時速度很快。當然這些優點 UTF-32 都具有,但很多人畢竟還是覺得 UTF-32 太佔空間了。
UTF-8 也不完美,也存在一些問題:
- 文化上的不平衡——對於歐美地區一些以英語為母語的國家 UTF-8 簡直是太棒了,因為它和 ASCII 一樣,一個字元只佔一個位元組,沒有任何額外的儲存負擔;但是對於中日韓等國家來說,UTF-8 實在是太冗餘,一個字元竟然要佔用 3 個位元組,儲存和傳輸的效率不但沒有提升,反而下降了。所以歐美人民常常毫不猶豫的採用 UTF-8,而我們卻老是要猶豫一會兒
- 變長位元組表示帶來的效率問題——大家對 UTF-8 疑慮重重的一個問題就是在於其因為是變長位元組表示,因此無論是計算字元數,還是執行索引操作效率都不高。為了解決這個問題,常常會考慮把 UTF-8 先轉換為 UTF-16 或者 UTF-32 後再操作,操作完畢後再轉換回去。而這顯然是一種效能負擔。
UTF-8 的優點:
- 字元空間足夠大,未來 Unicode 新標準收錄更多字元,UTF-8 也能妥妥的相容,因此不會再出現 UTF-16 那樣的尷尬
- 不存在大小端位元組序問題,資訊交換時非常便捷
- 容錯性高,區域性的位元組錯誤(丟失、增加、改變)不會導致連鎖性的錯誤,因為 UTF-8 的字元邊界很容易檢測出來,這是一個巨大的優點(正是為了實現這一點,咱們中日韓人民不得不忍受 3 位元組 1 個字元的苦日子)
大神如何選擇的呢?
因為無論是 UTF-8 和 UTF-16/32 都各有優缺點,因此選擇的時候應當立足於實際的應用場景。例如在大神的習慣中,儲存在磁碟上或進行網路交換時都會採用 UTF-8,而在程式內部進行處理時則轉換為 UTF-16/32。對於大多數簡單的程式來說,這樣做既可以保證資訊交換時容易實現相互相容,同時在內部處理時會比較簡單,效能也還算不錯。(基本上只要你的程式不是 I/O 密集型的都可以這麼幹,當然這只是大神粗淺的認識範圍內的經驗,很可能會被無情的反駁)
參考:
Unicode(UTF-8, UTF-16)令人混淆的概念
https://www.cnblogs.com/fnlingnzb-learner/p/6163205.html
Unicode中UTF-8與UTF-16編碼詳解
https://juejin.im/post/5ace27c96fb9a028dc416195