1. 程式人生 > >Javascript中的string型別使用UTF-16編碼

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 是為了解決傳統的字元編碼方案的侷限而產生的,它為每種語言中的每個字元設定了統一併且唯一的

二進位制編碼,以滿足跨語言、跨平臺進行文字轉換、處理的要求。1990年開始研發,1994年正式公佈。

通常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。那麼一個位元組所能表示的最多字元數就是28次方,也就是256。對於26個英文字母來說,大小寫全算上就是52個,再加上10個阿拉伯數字,62個字元,用可以表達256個不同字元的一個位元組來儲存是足夠了。

但是,我們中國的常用漢字就有3000多個,用一個只能表達256個字元的位元組顯然是不夠儲存的。至少也需要有2

個位元組,1個位元組是8個二進位制位,2個位元組就是16個二進位制位,最多可以表達216次方,也就是256*256=6553665536個字元足夠容納所有中國的漢字,外帶日語、韓語、阿拉伯語、稀其古怪語等等各種各樣的字元。所以這樣就產生了Unicode,因為它用2位元組表示字元,所以更嚴格來講應該叫UCS-2,後來因為怪字元太多,2位元組都不夠用了,所以又搞出來了一個4位元組表示的方法,稱作UCS-4。不過現在對絕大多數人來講UCS-2已經是足夠了。

 

Unicode本來是一個好東西,用2位元組表示65536種字元,全人類皆大歡喜的事情。但是偏偏有一幫子西洋人,非要認為這個東西是一種浪費,說我們英文就最多隻需要26個字母就夠了,1個位元組就夠了,為什麼要浪費2位元組呢?比如說字母A就是0100 0001,這一個位元組就夠了的東西,你弄2位元組,非要在前面加800000 0000 0100 0001,這不是浪費嗎?我們就偏要用1位元組表示英文。

好吧,我們全人類只好做妥協,規定每個位元組,只要看見0打頭的,就知道這是英文字母,這肯定不是漢字,只有看見1開頭的,才認為這是漢字。

但是我們漢字用1個位元組表示不下,那好辦,用21開頭的字元表示1個漢字。這樣本來16個二進位制位,減去2個開頭的1,只剩下14個二進位制位了,214次方就是16384個字元,對於中文來講,也是足夠用了。但是無奈他們還是想表達65536種字元,那怎麼辦呢?就需要3個位元組才能容納得下了,於是UTF-8粉墨登場。

首先,首位為0的字元被佔了,只要遇到0開頭的字元,就知道這是一個1位元組的字元,不必再往後數了,直接拿來用就可以,最多表示128種字元,從0000 00000111 1111,也就是從0127

接下來的事情就比較蹊蹺了。我們怎麼用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個有效位,27次方,最多可以表示128種字元。

2位元組的字元,110x xxxx 10xx xxxx,數一數,11x,所以是211次方,210次方是102411次方就是2048,很不幸,只能表示2048種字元,而我們的常用漢字就有3000多個,看來在這一區是放不下了,只好挪到3位元組。

3位元組的字元,1110 xxxx 10xx xxxx 10xx xxxx,數一數,16x216次方,最多可以表示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 並不是一個完美的選擇,它存在幾個方面的問題:

  1. UTF-16 能表示的字元數有 6 萬多,看起來很多,但是實際上目前 Unicode 5.0 收錄的字元已經達到 99024 個字元,早已超過 UTF-16 的儲存範圍;這直接導致 UTF-16 地位頗為尷尬——如果誰還在想著只要使用 UTF-16 就可以高枕無憂的話,恐怕要失望了
  2. UTF-16 存在大小端位元組序問題,這個問題在進行資訊交換時特別突出——如果位元組序未協商好,將導致亂碼;如果協商好,但是雙方一個採用大端一個採用小端,則必然有一方要進行大小端轉換,效能損失不可避免(大小端問題其實不像看起來那麼簡單,有時會涉及硬體、作業系統、上層軟體多個層次,可能會進行多次轉換)
  3. 另外,容錯性低有時候也是一大問題——區域性的位元組錯誤,特別是丟失或增加可能導致所有後續字元全部錯亂,錯亂後要想恢復,可能很簡單,也可能會非常困難。(這一點在日常生活裡大家感覺似乎無關緊要,但是在很多特殊環境下卻是巨大的缺陷)

目前支撐我們繼續使用 UTF-16 的理由主要是考慮到它是雙位元組的,在計算字串長度、執行索引操作時速度很快。當然這些優點 UTF-32 都具有,但很多人畢竟還是覺得 UTF-32 太佔空間了。

 UTF-8 也不完美,也存在一些問題:

  1. 文化上的不平衡——對於歐美地區一些以英語為母語的國家 UTF-8 簡直是太棒了,因為它和 ASCII 一樣,一個字元只佔一個位元組,沒有任何額外的儲存負擔;但是對於中日韓等國家來說,UTF-8 實在是太冗餘,一個字元竟然要佔用 3 個位元組,儲存和傳輸的效率不但沒有提升,反而下降了。所以歐美人民常常毫不猶豫的採用 UTF-8,而我們卻老是要猶豫一會兒
  2. 變長位元組表示帶來的效率問題——大家對 UTF-8 疑慮重重的一個問題就是在於其因為是變長位元組表示,因此無論是計算字元數,還是執行索引操作效率都不高。為了解決這個問題,常常會考慮把 UTF-8 先轉換為 UTF-16 或者 UTF-32 後再操作,操作完畢後再轉換回去。而這顯然是一種效能負擔。

UTF-8 的優點:

  1. 字元空間足夠大,未來 Unicode 新標準收錄更多字元,UTF-8 也能妥妥的相容,因此不會再出現 UTF-16 那樣的尷尬
  2. 不存在大小端位元組序問題,資訊交換時非常便捷
  3. 容錯性高,區域性的位元組錯誤(丟失、增加、改變)不會導致連鎖性的錯誤,因為 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