轉:【字符編碼】徹底理解字符編碼

轉:https://www.cnblogs.com/leesf456/p/5317574.html

 

【字符編碼】徹底理解字符編碼

 

一、前言

  在解決昨天的問題時,又引出了很多新的問題,如爲什麼要進行編碼,這些編碼的關係如何,如ASCII,IOS-8859-1,GB2312,GBK,Unicode之間的關係,筆者想要徹底理解字符編碼背後的故事,遂進行了探索,具體筆記如下。如園友能讀完本篇文章,我相信會解開很多疑惑。

二、字符編碼

  2.1 爲何需要編碼?

  我們知道,所有的信息最終都表示爲一個二進制的字符串,每一個二進制位(bit)有0和1兩種狀態。當我們需要把字符'A'存入計算機時,應該對應哪種狀態呢,存儲時,我們可以將字符'A'用01000010(這個隨便編的)二進制字符串表示,存入計算機;讀取時,再將01000010還原成字符'A'。那麼問題來了,存儲時,字符'A'應該對應哪一串二進制數呢,是01000010?或者是10000000 11110101?說白了,就是需要一個規則。這個規則可以將字符映射到唯一一種狀態(二進制字符串),這就是編碼。而最早出現的編碼規則就是ASCII編碼,在ASCII編碼規則中,字符'A'既不對應01000010,也不對應1000 0000 11110101,而是對應01000001(不要問爲什麼,這是規則)。

  2.2 ASCII

  這套編碼規則是由美國定製,一共規定了128個字符的編碼,比如空格"SPACE"是32(十進制)(二進制00100000),大寫的字母A是65(二進制01000001)。這128個符號(包括 32個不能打印出來的控制符號),只佔用了一個字節(8 bit)的後面7位,最前面的1位統一規定爲0。總共纔有128個字符編碼,一個字節都沒有用完,這好像似乎有點太少了。於是乎,就開始壓榨最高位,對其爲1時也進行編碼,利用最高位進行編碼的方式就稱爲非ASCII編碼,如ISO-8859-1編碼。

  2.3 ISO-8859-1

  這套編碼規則由ISO組織制定。是在 ASCII 碼基礎上又制定了一些標準用來擴展ASCII編碼,即 00000000(0) ~ 01111111(127) 與ASCII的編碼一樣,對 10000000(128) ~ 11111111(255)這一段進行了編碼,如將字符§編碼成 10100111(167)。ISO-8859-1編碼也是單字節編碼,最多能夠表示256個字符。Latin1是ISO-8859-1的別名,有些環境下寫作Latin-1。但是,即使能夠表示256個字符,對中文而言,還是太少了,一個字節肯定不夠,必須用多個字節表示。但是,由於是單字節編碼,和計算機最基礎的表示單位一致,所以很多時候,仍舊使用 ISO8859-1編碼來表示。而且在很多協議上,默認使用該編碼。比如,雖然"中文"兩個字不存在ISO8859-1編碼,以GB2312編碼爲例,應該是D6D0 CEC4兩個字符,使用ISO8859-1編碼的時候則將它拆開爲4個字節來表示:D6D0 CEC4(事實上,在進行存儲的時候,也是以字節爲單位進行處理)。而如果是UTF編碼,則是6個字節e4 b8 ad e6 96 87。很明顯,這種表示方法還需要以另一種編碼爲基礎才能正確顯示。而常見的中文編碼方式有GB2312、BIG5、GBK。

  2.4 GB2312

  GB2312其對所收錄字符進行了"分區"處理,共94個區,區從1(十進制)開始,一直到94(十進制),每區含有94個位,位從1(十進制)開始,一直到94(十進制),共8836(94 * 94)個碼位,這種表示方式也稱爲區位碼,GB2312是雙字節編碼,其中高字節表示區,低字節表示位。各區具體說明如下:

01-09區收錄除漢字外的682個字符,有164個空位(9 * 94 - 682)。
10-15區爲空白區,沒有使用。
16-55區收錄3755個一級漢字(簡體),按拼音排序。
56-87區收錄3008個二級漢字(簡體),按部首/筆畫排序。
88-94區爲空白區,沒有使用。

  那麼根據區位碼如何算出GBK2312編碼呢?區位碼的表示範圍爲0101 - 9494(包含了空的區位碼)。點擊這裏,查看中GB2312編碼區位碼。之後只需要按照如下規則進行轉化即可。

  1. 將區(十進制)轉化爲十六進制。

  2. 將轉化的十六進制加上A0,得到GB2312編碼的高字節。

  3. 將位(十進制)轉化爲十六進制。

  4. 將轉化的十六進制加上A0,得到GB2312編碼的低字節。

  5. 組合區和位,區在高字節,位在低字節。

  6. 得到GB2312編碼。

  具體的流程圖如下:

  例如:'李'字的區位碼爲3278(表示在32區,78位)。1. 將32(區)轉化爲十六進制爲20。2. 加上A0爲C0。3. 將78(位)轉化爲十六進制爲4E。4. 加上A0爲EE。5. 組合區和位,爲C0EE。6. 得到GB2312編碼,即'李'字的GB2312編碼爲C0EE。

  GB2312用兩個字節編碼,採用分區編碼,總共編碼的中文個數爲6763(3755 + 3008)。這些漢字只是最常用的漢字,已經覆蓋中國大陸99.75%的使用頻率。但是,還有一些漢字在GB2312中沒有被編碼,如'鎔'字,在GB2312中就沒有被編碼,這樣就導致了問題,隨之就出現了主流的GBK編碼。在講解GBK編碼之前,我們另外講解一下BIG5編碼。

  2.5 BIG5

  BIG5採用雙字節編碼,使用兩個字節來表示一個字符。高位字節使用了0x81-0xFE,低位字節使用了0x40-0x7E,及0xA1-0xFE。該編碼是繁體中文字符集編碼標準,共收錄13060箇中文字,其中有二字爲重複編碼,即“兀、兀”(A461及C94A)和“嗀、嗀”(DCD1及DDFC)。具體的分區如下:  

複製代碼
8140-A0FE 保留給使用者自定義字符(造字區)
A140-A3BF 標點符號、希臘字母及特殊符號。其中在A259-A261,收錄了度量衡單位用字:兙兛兞兝兡兣嗧瓩糎。
A3C0-A3FE 保留。此區沒有開放作造字區用。
A440-C67E 常用漢字,先按筆劃再按部首排序。
C6A1-F9DC 其它漢字。
F9DD-F9FE 製表符。
複製代碼

  點擊這裏,查看BIG5編碼。注意,BIG5編碼與GBK編碼沒有什麼關係。

  2.6 GBK

  GBK編碼擴展了GB2312,完全兼容GB2312編碼(如'李'字的GBK、GB2312編碼均爲C0EE),但其不兼容BIG5編碼('長'字的BIG5編碼爲AAF8,GBK編碼爲E94C,'李'字的BIG5編碼爲A7F5 不等於C0EE),即如果使用GB2312編碼,使用GBK解碼是完全正常的,但是如果使用BIG5編碼,使用GBK解碼,會出現亂碼。相比於GB2312編碼,GBK編碼了更多漢字,如'鎔'字。GBK編碼依然採用雙字節編碼方案,其編碼範圍:8140-FEFE,剔除xx7F碼位,共23940個碼位。能表示 21003 個漢字。點擊這裏,查看GBK編碼。點擊這裏,可以查詢中文的其他編碼。在GBK之後又出現了GB18030編碼,但是沒有形成主流,故不做介紹,至此,中文編碼的問題已經講解完成。那麼問題又來了,大陸網民與在海峽兩岸網民交流時,若都使用GBK編碼,則沒有問題,若一方使用GBK編碼,一方使用BIG5編碼,那麼就會出現亂碼問題,這是在海峽兩岸網民交流,如果漂洋過海進行交流呢?那就更容易出現亂碼問題,這時候我們可能想,要是有一套全世界都通用的編碼就好了,不要擔心,這樣的編碼確實是存在的,那就是Unicode。

  2.7 Unicode

  有兩個獨立的, 創立單一字符集的嘗試. 一個是國際標準化組織(ISO)的 ISO 10646 項目, 另一個是由多語言軟件製造商組成的協會組織的 Unicode 項目. 在1991年前後, 兩個項目的參與者都認識到, 世界不需要兩個不同的單一字符集. 它們合併雙方的工作成果, 併爲創立一個單一編碼表而協同工作. 兩個項目仍都存在並獨立地公佈各自的標準, 但 Unicode 協會和 ISO/IEC JTC1/SC2 都同意保持 Unicode 和 ISO 10646 標準的碼錶兼容, 並緊密地共同調整任何未來的擴展。

  Unicode是指一張表,裏面包含了可能出現的所有字符,每個字符對應一個數字,這個數字稱爲碼點(Code Point),如字符'H'的碼點爲72(十進制),字符'李'的碼點爲26446(十進制)。Unicode表包含了1114112個碼點,即從000000(十六進制) - 10FFFF(十六進制)。地球上所有字符都可以在Unicode表中找到對應的唯一碼點。點擊這裏,查詢字符對應的碼點。Unicode將碼空間劃分爲17個平面,從00 - 10(十六進制,最高兩位),即從0 - 16(十進制),每個平面有65536個碼點(2^16)其中最重要的是第一個Unicode平面(碼位從0000 - FFFF),包含了最常用的字符,該平面被稱爲基本多語言平面(Basic Multilingual Plane),縮寫爲BMP,其他平面稱爲輔助平面(Supplementary Planes),在基本多文種平面內, 從D800到DFFF之間的碼位區段是永久保留不映射到字符的, 因此UTF-16編碼巧妙的利用了這保留下來的碼位來對輔助平面內的字符進行編碼,這點後面進行講解。Unicode只是一個符號集,只規定的字符所對應的碼點,並沒有指定如何存儲,如何進行存儲出現了不同的編碼方案,關於Unicode編碼方案主要有兩條主線:UCS和UTF。UTF主線由Unicode Consortium進行維護管理,UCS主線由ISO/IEC進行維護管理。

  2.8 UCS

  UCS全稱爲"Universal Character Set",在UCS中主要有UCS-2和UCS-4。

  1. UCS-2

  UCS-2是定長字節的,固定使用2個字節進行編碼,從0000(十六進制)- FFFF(十六進制)的碼位範圍,對應第一個Unicode平面。採用BOM(Byte Order Mark)機制,該機制作用如下:1. 確定字節流採用的是大端序還是小端序。2. 確定字節流的Unicode編碼方案。

  2. UCS-4

  UCS-4是定長字節的,固定使用4個字節進行編碼。也採用了BOM機制。

  2.9 UTF

  UTF全稱爲"Unicode Transformation Format",在UTF中主要有UTF-8,UTF-16和UTF-32。

  1. UTF-8

  UTF-8是一種變長編碼方式,使用1-4個字節進行編碼。UTF-8完全兼容ASCII,對於ASCII中的字符,UTF-8採用的編碼值跟ASCII完全一致。UTF-8是Unicode一種具體的編碼實現。UTF-8是在互聯網上使用最廣的一種Unicode的編碼規則,因爲這種編碼有利於節約網絡流量(因爲變長編碼,而非統一長度編碼)。關於Unicode碼點如何轉化爲UTF-8編碼,可以參照如下規則:

  ① 對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的unicode碼。因此對於英語字母,UTF-8編碼和ASCII碼是相同的。

  ② 對於n字節的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一律設爲10。剩下的沒有提及的二進制位,全部爲這個符號的unicode碼。

  總結的編碼規則如下:

複製代碼
      Unicode符號範圍                   |   UTF-8編碼方式
         (十六進制) (十進制)            |   (二進制)
  ----------------------------------------------------------------------------------------------------
    0000 0000-0000 007F (0-127)           |    0xxxxxxx
    0000 0080-0000 07FF (128-2047)        |    110xxxxx 10xxxxxx
    0000 0800-0000 FFFF (2048-65535)      |     1110xxxx 10xxxxxx 10xxxxxx
    0001 0000-0010 FFFF (65536-1114111)   |    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
複製代碼

  說明:字符'A'的Unicode碼點爲65(十進制),根據上表,在第一行範圍,則字符'A'的UTF-8編碼爲01000001,中文字符'李'的Unicode碼點爲26446(十進制),二進制爲01100111 01001110,十六進制爲674E。根據上表,在第三行範圍,則將'李'二進制代碼從低位到高位依次填入x中,不足的填入0。得到UTF-8編碼爲11100110 10011101 10001110,即E69D8E(十六進制)。

  由上述編碼規則可知,0000 0000 - 0000 FFFF(第一行到第三行)爲Unicode第一個平面(基本多語言平面),而0001 0000 - 10 FFFF(第四行)爲Unicode其他平面(輔助平面)。在基本多語言平面對應了絕大多數常用的字符。對於大於65535(十進制)的碼點,即在輔助平面上的碼點,需要使用4個字節來進行UTF-8編碼。

  2. UTF-16

  UTF-8是不定長的編碼,使用1、2、3、4個字節編碼,而UTF-16則只使用2或4個字節編碼。UTF-16也是Unicode一種具體的編碼實現。關於Unicode如何轉化爲UTf-16編碼規則如下

  ① 若Unicode碼點在第一平面(BPM)中,則使用2個字節進行編碼。

  ② 若Unicode碼點在其他平面(輔助平面),則使用4個字節進行編碼。

  關於輔助平面的碼點編碼更詳細解析如下:輔助平面碼點被編碼爲一對16比特(四個字節)長的碼元, 稱之爲代理對(surrogate pair), 第一部分稱爲高位代理(high surrogate)或前導代理(lead surrogates),碼位範圍爲:D800-DBFF. 第二部分稱爲低位代理(low surrogate)或後尾代理(trail surrogates), 碼位範圍爲:DC00-DFFF。注意,高位代理的碼位從D800到DBFF,而低位代理的碼位從DC00到DFFF,總共恰好爲D800-DFFF,這部分碼點在第一平面內是保留的,不映射到任何字符,所以UTF-16編碼巧妙的利用了這點來進行碼點在輔助平面內的4字節編碼。

  說明:字符'A'的Unicode碼點爲65(十進制),十六進制表示爲41,在第一平面。根據規則,UTF-16採用2個字節進行編碼。那麼問題又來了,知道了採用兩個字節編碼,並且我們也知道計算機是以字節爲單位進行存儲,這兩個字節應該表示爲00 41(十六進制)?或者是41 00(十六進制)呢?這就引出了一個問題,需要用到之前提及的BOM機制來解決。

  表示爲00 41意味着採用了大端序(Big endian),而表示爲41 00意味着採用了小端序。那麼計算機如何知道存儲的字符信息採用了大端序還是小端虛呢?這就需要加入一些控制信息,具體是採用大端序,則在文件前加入FE FF,採用小端序,則在文件前加入FF FE。這樣,當計算開始讀取時發現前兩個字節爲FE FF,就表示之後的信息採用的是小端序,反之,則是大端序。

  字符 (無法顯示,只能截圖顯示),其Unicode碼點爲65902(十進制),十六進制爲1016E,很顯然,已經超出了第一平面(BMP)所能表示的範圍。其在輔助平面內,根據規則,UTF-16採用4個字節進行編碼。然而其編碼不是簡單擴展爲4個字節(00 01 01 6E),而是採用如下規則進行計算。

  ① 使用Unicode碼位減去100000(十六進制),得到的值擴展20位(因爲Unicode最大爲10 FF FF(十六進制),減去1 00 00(十六進制)後,得到的結果最大爲0FFF FF(十六進制),即爲20位,不足20位的,在高位加一個0,擴展至20位即可)。

  ② 將步驟一得到的20位,按照高十位和低十位進行分割。

  ③ 將步驟二的高十位擴展至2個字節,再加上D800(十六進制),得到高位代理或前導代理。取值範圍是D800 - 0xDBFF。

  ④ 將步驟二的低十位擴展至2個字節,再加上DC00(十六進制),得到低位代理或後尾代理。取值範圍是DC00 - 0xDFFF。

  Unicode轉UTF-16規則流程圖如下:

 

  按照這個規則,我們計算字符的UTF-16編碼,我們知道其碼點爲1016E,減去10000得到016E,擴展至0016E,進行分割,得到高十位爲00 0000 0000,十六進制爲0000,加上D800爲D800;得到低十位爲01 0110 1110,十六進制爲016E,加上DC00爲DD6E;綜合得到D8 00 DD 6E。即UTF-16編碼爲D8 00 DD 6E(也可爲D8 0 DD 6E)。

  而對於UTF-32是使用4個字節表示,也採用BOM機制,可以類比UTF-16,這裏不再額外介紹。

四、字符編碼區別

  4.1 UCS-2 與 UTF-16區別

  從上面的分析知道,UCS-2採用的兩個字節進行編碼。在0000到FFFF的碼位範圍內,它和UTF-16基本一致,爲什麼說基本一致,因爲在UTF-16中從U+D800到U+DFFF的碼位不對應於任何字符,而在使用UCS-2的時代,U+D800到U+DFFF內的值被佔用。

  UCS-2只能表示BMP內的碼點(只採用2個字節),而UTF-16可以表示輔助平面內的碼點(採用4個字節)。

  我們可以抽象的認爲UTF-16可看成是UCS-2的父集。在沒有輔助平面字符(surrogate code points)前,UTF-16與UCS-2所指的意思基本一致。但當引入輔助平面字符後,想要表示輔助平面字符時,就只能用UTF-16編碼了。

  4.2 UCS -4與 UTF-16的區別

  在BMP上,UTF-16採用2個字節表示,而在輔助平面上,UTF-16採用的是4個字節表示。對於UCS-4,不管在哪個平面都採用的是四個字節表示。

  4.3 爲什麼UTF-8編碼不需要BOM機制

  因爲在UTF-8編碼中,其自身已經帶了控制信息,如1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx,其中1110就起到了控制作用,所以不需要額外的BOM機制。

五、總結

  如果讀者有耐心看到這裏,我相信對於字符編碼這一塊已經就已經沒有什麼疑問了。寫到這裏,就完成了主流編碼的探索,探索的過程確實是不容易,最後弄清楚了,感覺相當的快樂,也把經驗總結分享給各位園友,如果讀者有任何疑問,歡迎交流,謝謝各位園友的觀看~

 

參考鏈接:

http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html#comment-text

http://www.joelonsoftware.com/articles/Unicode.html

http://blog.csdn.net/xys_777/article/details/5773763

http://www.zhihu.com/question/19817672

http://demon.tw/programming/utf-16-ucs-2.html

http://blog.csdn.net/dslztx/article/details/48830887

http://blog.csdn.net/dslztx/article/details/48947097

http://www.zhihu.com/question/22881537

http://blog.csdn.net/shangboerds/article/details/7498317

http://blog.csdn.net/shuilan0066/article/details/7865715

http://www.zhihu.com/question/23374078

http://swiftlet.net/archives/category/char-encoding

http://blog.csdn.net/shuilan0066/article/details/7839189

http://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/

http://www.freebuf.com/articles/others-articles/25623.html

http://blog.csdn.net/qinysong/article/details/1179513

http://unicode-table.com/cn/

PS:如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”,將會是我不竭的動力!
作者:leesf    掌控之中,纔會成功;掌控之外,註定失敗。
出處:http://www.cnblogs.com/leesf456/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
如果覺得本文對您有幫助,您可以請我喝杯咖啡!
 

 
標籤: Java字符編碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章