編碼研究筆記

本次討論的編碼涉及到Unicode、傳統的ascii、和ascii的擴展集ansi中的中國編碼、西文編碼。java

老實說老師都一直以ascii編碼講解文字的編碼,漢子編碼則是一筆帶過,害我在很長時間誤認爲電腦世界中只有一種編碼,若是我是個以英語爲母語的人的話,好像沒什麼問題,不巧我是個中國人,就不可能靠這種誤解順利認識整個編碼世界windows

編碼樣式很是之多,但經常使用的也就那麼幾個,其中以我國爲例,國內經常使用的編碼格式有gb23十二、gbk、utf-八、utf-1六、iso-8859-1,其中iso-8859-1格式的普遍應用,並非由於他多麼優秀,相反,他只對西文字符編碼,徹底不可能表示漢子,對國內來講,這有時是個災難,只是由於國內的軟件產品大都是從國外傳遞過來的,他們採用的編碼格式默認就會是iso-8859-1。網絡

這裏首先解釋一個概念性的問題,編碼方式和實現方式這兩個概念的區別,編碼方式是指對數字進行組織,選取一個具體的範圍,而後將範圍中的數字設置爲碼位,爲每一個碼位編制一個文字符號,這樣文字符號就和數字創建了一一對應的關係,而實現方式則是指將數字轉換到程序數據的編碼方案。舉個例子,unicode爲編碼方式,而utf-八、utf-16則爲實現方式。要知道unicode是用0-0x10FFFF文字範圍來進行文字符號映射的,而計算機則是以二進制八位爲基本單位的,若是能讓程序識別,顯然須要一個將編碼方式映射具體字符的數字經過某種方式轉化爲程序數據,所以實現方式就應運而生了,utf-八、utf-16就產生了。編碼

咱們先從如今世界範圍內應用最廣的unicode開始,概念性的東西咱就一筆帶過吧,Unicode是國際組織制定的能夠容納世界上全部文字和符號的字符編碼方案,unicode編碼範圍爲0-0x10FFFF,能夠看到若是用二進制表示,最大值0x10FFFF其實是spa

10000  11111111  11111111,最多能夠容納1114112個字符,這裏要澄清一個你們可能會產生模糊的概念意義,UCSUnicode的關係,通用字符集(Universal Character SetUCS)是由ISO制定的ISO 10646(或稱ISO/IEC 10646)標準所定義的標準字符集。UCS-2用兩個字節編碼,UCS-44個字節編碼。而Unicode 是基於通用字符集(Universal Character Set)的標準來發展,歷史上存在兩個獨立的嘗試創立單一字符集的組織,即國際標準化組織(ISO)和多語言軟件製造商組成的統一碼聯盟。前者開發的 ISO/IEC 10646 項目,後者開發的統一碼項目。所以最初制定了不一樣的標準。設計

1991年先後,兩個項目的參與者都認識到,世界不須要兩個不兼容的字符集。因而,它們開始合併雙方的工做成果,併爲創立一個單一編碼表而協同工做。從Unicode 2.0開始,Unicode採用了與ISO 10646-1相同的字庫和字碼;ISO也承諾,ISO 10646將不會替超出U+10FFFF的UCS-4編碼賦值,以使得二者保持一致。兩個項目仍都存在,並獨立地公佈各自的標準。但統一碼聯盟和ISO/IEC JTC1/SC2都贊成保持二者標準的碼錶兼容,並緊密地共同調整任何將來的擴展。在發佈的時候,Unicode通常都會採用有關字碼最多見的字型,但ISO 10646通常都儘量採用Century字型。代理

Unicode在實現上仍是和UCS有一些差距的,標準UCS-4根據最高位爲0的最高字節分紅2^7=128group。每一個group再根據次高字節分爲256個平面(plane)。每一個平面根據第3個字節分爲256行 (row),每行有256個碼位(cell)。group 0的平面0被稱做BMPBasic Multilingual Plane)。將UCS-4BMP去掉前面的兩個零字節就獲得了UCS-2。每一個平面有2^16=65536個碼位。Unicode計劃使用了17個平面(注意unicode並只是用了group0),一共有17*65536=1114112個碼位。在Unicode 5.0.0版本中,已定義的碼位只有238605個,分佈在平面0、平面1、平面2、平面14、平面15、平面16。其中平面15和平面16上只是定義了兩個各佔65534個碼位的專用區(Private Use Area),分別是0xF0000-0xFFFFD0x100000-0x10FFFD。所謂專用區,就是保留給你們放自定義字符的區域,能夠簡寫爲PUAcode

  平面0也有一個專用區:0xE000-0xF8FF,有6400個碼位。平面0的0xD800-0xDFFF,共2048個碼位,是一個被稱做代理區(Surrogate)的特殊區域。代理區的目的用兩個UTF-16字符表示BMP之外的字符。這個待會再解釋。orm

       注意,在Unicode 5.0.0版本中,238605-65534*2-6400-2048=99089。餘下的99089個已定義碼位分佈在平面0、平面1、平面2和平面14上,它們對應着Unicode目前定義的99089個字符,其中包括71226個漢字。平面0、平面1、平面2和平面14上分別定義了52080341943253337個字符。平面243253個字符都是漢字。平面0上定義了27973個漢字。能夠很清楚的看到,平面2也有不少漢字,咱們經常使用的漢字大都在平面0,但若是咱們要用平面2的漢字時,就意味着這個碼數用兩個二進制的字節是裝不下的,而兩個字節是utf-16的代碼單元的大小,顯然這須要必定的轉換手段,並且,能夠很肯定的是,這個漢字是不能用雙字節表示的,因此要注意,由於java內部用的編碼方式就是utf-16,雙字節是常態,但若是高於兩個字節就可能出現什麼問題了,好比java中的char基本類型實際上大小爲兩個字節,當他要表示一個大於兩個字節的代碼點時,顯然,是不可表示的,因此應該認識到,char類型並不能表示全部的字符,他只能表示平面0中的字符,對於其餘平面的字符,他就無能爲力了,所以,應該慎用char類型,建議用String替代char類型。utf-8

       先來說解utf-16的實現方式,UTF-16編碼以16位無符號整數爲單位。咱們把Unicode編碼記做U。編碼規則以下:

  若是U<0x10000,U的UTF-16編碼就是U對應的16位無符號整數

  若是U≥0x10000,咱們先計算U'=U-0x10000,而後將U'寫成二進制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16編碼(二進制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。

爲何U'能夠被寫成20個二進制位?Unicode的最大碼位是0x10ffff,減去0x10000後,U'的最大值是0xfffff,因此確定能夠用20個二進制位表示。例如:Unicode編碼0x20C30,減去0x10000後,獲得0x10C30,寫成二進制是:0001 0000 1100 0011 0000。用前10位依次替代模板中的y,用後10位依次替代模板中的x,就獲得:1101100001000011 1101110000110000,即0xD843 0xDC30。

  按照上述規則,Unicode編碼0x10000-0x10FFFF的UTF-16編碼有兩個WORD,第一個WORD的高6位是110110,第二個WORD的高6位是110111。可見,第一個WORD的取值範圍(二進制)是11011000 00000000到11011011 11111111,即0xD800-0xDBFF。第二個WORD的取值範圍(二進制)是11011100 00000000到11011111 11111111,即0xDC00-0xDFFF。

  爲了將一個WORD的UTF-16編碼與兩個WORD的UTF-16編碼區分開來,Unicode編碼的設計者將0xD800-0xDFFF保留下來,並稱爲代理區(Surrogate):

D800-DB7F ║ High Surrogates ║ 高位替代

DB80-DBFF ║ High Private Use Surrogates ║ 高位專用替代

DC00-DFFF ║ Low Surrogates ║ 低位替代

  高位替代就是指這個範圍的碼位是兩個WORD的UTF-16編碼的第一個WORD。低位替代就是指這個範圍的碼位是兩個WORD的UTF-16編碼的第二個WORD。那麼,高位專用替代是什麼意思?咱們來解答這個問題,順便看看怎麼由UTF-16編碼推導Unicode編碼。

  若是一個字符的UTF-16編碼的第一個WORD在0xDB80到0xDBFF之間,那麼它的Unicode編碼在什麼範圍內?咱們知道第二個WORD的取值範圍是0xDC00-0xDFFF,因此這個字符的UTF-16編碼範圍應該是0xDB80 0xDC00到0xDBFF 0xDFFF。咱們將這個範圍寫成二進制:

1101101110000000 11011100 00000000 - 1101101111111111 1101111111111111

  按照編碼的相反步驟,取出高低WORD的後10位,並拼在一塊兒,獲得

1110 0000 0000 0000 0000 - 1111 1111 1111 1111 1111  XML即0xe0000-0xfffff,按照編碼的相反步驟再加上0x10000,獲得0xf0000-0x10ffff。這就是UTF-16編碼的第一個WORD在0xdb80到0xdbff之間的Unicode編碼範圍,即平面15和平面16。由於Unicode標準將平面15和平面16都做爲專用區,因此0xDB80到0xDBFF之間的保留碼位被稱做高位專用替代。

再次注意,非BMP的文字字符在用utf-16表示時是用四個字節表示的,也即雙字,在java中,utf-16的代碼單元是兩個字節,也即以char爲基本單位,而unicode中,每個碼位叫作一個代碼點,通常在字符碼數在BMP下時,一個代碼點對應一個代碼單元,可是若是超過BMP,這就不會是一一對應的關係了,在這裏還要說一個題外話,一個代碼點可能對應者多個代碼單元,哪個代碼點能夠表示一個字符嗎?這在unicode中是能夠的,但在latin-1也即iso-8859-1中這可能就存在變數了,latin-1能夠看作是對傳統ascii的擴展,其採用單字節編碼方式,所以,其最大能表示255個字符,這對西文字符看起來足夠了,可是還會有一些特殊字符或者將來可能誕生的字符,所以他們會採用多個代碼點表示一個字符,這些字符多是一個組合字符

  下面講解utf-8,

UTF-8以字節爲單位對Unicode進行編碼。從UnicodeUTF-8的編碼方式以下:

Unicode編碼(16進制) ║ UTF-8 字節流(二進制)

000000 - 00007F ║ 0xxxxxxx

000080 - 0007FF ║ 110xxxxx 10xxxxxx

000800 - 00FFFF ║ 1110xxxx 10xxxxxx 10xxxxxx

010000 - 10FFFF ║ 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8的特色是對不一樣範圍的字符使用不一樣長度的編碼。對於0x00-0x7F之間的字符,UTF-8編碼與ASCII編碼徹底相同。UTF-8編碼的最大長度是4個字節。從上表能夠看出,4字節模板有21個x,便可以容納21位二進制數字。Unicode的最大碼位0x10FFFF也只有21位。

注意首字節的標誌位,有幾個1就表明有幾個字符,標誌位中的0做爲標誌位的結尾標誌

例1:「漢」字的Unicode編碼是0x6C49。0x6C49在0x0800-0xFFFF之間,使用用3字節模板了:1110xxxx 10xxxxxx 10xxxxxx。將0x6C49寫成二進制是:0110 1100 0100 1001, 用這個比特流依次代替模板中的x,獲得:11100110 10110001 10001001,即E6 B1 89。

  例2:Unicode編碼0x20C30在0x010000-0x10FFFF之間,使用用4字節模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。將0x20C30寫成21位二進制數字(不足21位就在前面補0):0 0010 0000 1100 0011 0000,用這個比特流依次代替模板中的x,獲得:11110000 10100000 10110000 10110000,即F0 A0 B0 B0。

下面對編碼unicode作一個整體的歸納,如今用的最多的是utf-8,由於對於至少雙字節的utf-16來講,靈活字節數可變的編碼方式能夠有效的減小字節數,這對於在網絡帶寬有限的今天,這是頗有意義的,並且,計算機中的不少數據是用西文字符表示的,utf-8在表示西文時只用了一個字符,這顯然會比雙字節的utf-16有優點,還有一點,utf-8的編碼方式有助於自己代碼點的校驗,能夠不用藉助外界的校驗方式,在複雜多變的網絡世界,出錯是常態,採用自己的特色校驗能夠有效的將損失降至最低,由於一個字節的缺失只會對當前代碼點有影響,能夠很容易定位到出錯代碼點,對其餘代碼點沒有影響,而utf-16則沒有這種特性,若是出現字節缺失,出錯點後的字節可能都會解碼都會出現問題,與外界校驗想比,utf-8能夠達到細粒化的校驗,而外界校驗則是整體性的,具備不肯定性,並且外界校驗一旦出錯,就直接丟棄,而utf-8則是能夠繼續解碼的,固然utf-8也是優缺點的,仔細想一想,其編碼解碼方式都是至關耗時的,相對於utf-8,utf-16編碼解碼方式則很是簡單,對於以效率爲主要目並且出錯率很底的的單機,utf-16顯然更具備優點。

下面討論常見的BOM也即字節序問題,根據字節序的不一樣,UTF-16能夠被實現爲UTF-16LE或UTF-16BE

那麼,怎麼判斷字節流的字節序呢?Unicode標準建議用BOM(Byte Order Mark)來區分字節序,即在傳輸字節流前,先傳輸被做爲BOM的字符"零寬無中斷空格"。這個字符的編碼是FEFF,而反過來的FFFE(UTF-16)在Unicode中都是未定義的碼位,不該該出如今實際傳輸中。下表是各類UTF編碼的BOM:

UTF編碼 ║ Byte Order Mark

UTF-8 ║ EF BB BF

UTF-16LE ║ FF FE

UTF-16BE ║ FE FF

事實上,utf-8是沒有字節序問題的,所以BOM可加可不加,所以會有utf-8和utf-8 without BOM,注意這二者是等價的,沒有所謂的常態、特殊態,utf-8帶BOM也是有好處的,咱們能夠經過BOM來識別其是哪一種編碼方式,固然,這僅僅限定在unicode系列,utf-16是存在字節序問題的,所以其必定會有BOM

下面講解GBK 和GB2312

下面爲Copy的概念

GBK即漢字內碼擴展規範,K爲擴展的漢語拼音中「擴」字的聲母。英文全稱Chinese Internal Code SpecificationGBK編碼標準兼容GB2312,共收錄漢字21003個、符號883個,並提供1894個造字碼位,簡、繁體字融於一庫。GB2312碼是中華人民共和國國家漢字信息交換用編碼,全稱《信息交換用漢字編碼字符集——基本集》,1980年由國家標準總局發佈。基本集共收入漢字6763個和非漢字圖形字符682個,通行於中國大陸。新加坡等地也使用此編碼。GBK是對GB2312-80的擴展,也就是CP936字碼表 (Code Page 936)的擴展(以前CP936GB 2312-80如出一轍)。

GBK採用雙字節表示,整體編碼範圍爲8140-FEFE,首字節在81-FE 之間,尾字節在40-FE 之間,剔除 xx7F一條線。總計23940 個碼位,共收入21886個漢字和圖形符號,其中漢字(包括部首和構件)21003 個,圖形符號883 個。P-Windows3.2和蘋果OS以GB2312爲基本漢字編碼, Windows 95/98則以GBK爲基本漢字編碼。

 字符有一字節和雙字節編碼,00–7F範圍內是一位,和ASCII保持一致,此範圍內嚴格上說有96個字符和32個控制符號。

  以後的雙字節中,前一字節是雙字節的第一位。整體上說第一字節的範圍是81–FE(也就是不含80和FF),第二字節的一部分領域在40–7E,其餘領域在80–FE。

Copy完畢

注意,gb系列是兼容ascii的,基本上全部的編碼格式都是兼容ascii,即便雙字節的utf-16也是高位爲0,低位爲ascii的,要知道在ascii中,0是無心義的,所以,即便文字採用其餘編碼格式編碼,採用ascii解碼都是能夠看到西文字符的,西文字符是不會亂碼的

這裏討論一個有趣的問題,就是給你一個二進制文件,你怎麼肯定採用正確的解碼方式解析這個文件以顯示正確的字符,這其實是有一些困難的,讓咱們針對幾種方式進行討論,首先是unicode系列,由於其有BOM,能夠經過BOM識別utf-16和部分帶BOM的utf-8,所以首先是utf-8 without BOM 和gb系列的比較,gb編碼範圍爲8140-FEFE,首字節在81-FE 之間,尾字節在40-FE 之間,而utf-8在雙字節時範圍爲

C080 到dfbf,實際上這個範圍是在gb的編碼範圍的,若是文件中的全部字符通過gb編碼後都是在C080 dfbf這個範圍,那麼編碼格式就是不可肯定的,在中文環境的windows中,若是在文本文件中寫入「聯通」時,在保存時默認是爲gbk(即ansi),當在打開時,由於這兩個字通過gbk編碼範圍都在C080 dfbf,而windows默認彷佛是按unicode編碼格式打開,在肯定無BOM後,其按utf-8 without BOM處理,當有數字不在utf-8編碼範圍時,其就認爲此文件編碼格式不是utf-8,轉而用本地編碼格式,在中文環境下就是gbk,注意windows假定文件沒有出現錯誤,這是不嚴謹的,由於「聯通」在utf-8的範圍,其就會按照utf-8解碼,顯然會是亂碼,還有會出現四字節的衝突狀況,在四字節上,gb兩個文字整體範圍會是81408140 fefefefe,而utf-8四字節表示範圍爲f0808080f7bfbfbf,這顯然在gb四字節的範圍內,所以也可能出現衝突狀況,至於三字節,經過分析不會出現衝突狀況

相關文章
相關標籤/搜索