標籤:

徹底搞懂編碼 GBK 和 UTF8

常用編碼格式一覽

首先來看一下常用的編碼有哪些,截圖自Notepad++。其中ANSI在中國大陸即為GBK(以前是GB2312),最常用的是 GBK 和 UTF8無BOM 編碼格式。後面三個都是有BOM頭的文本格式,UCS-2即為人們常說的Unicode編碼,又分為大端、小端。

所謂BOM頭(Byte Order Mark)就是文本文件中開始的幾個並不表示任何字元的位元組,用二進位編輯器(如bz.exe)就能看到了。

  1. UTF8的BOM頭為 0xEF 0xBB 0xBF
  2. Unicode大端模式為 0xFE 0xFF
  3. Unicode小端模式為 0xFF 0xFE

何為GBK,何為GB2312,與區位碼有何淵源?

區位碼是早些年(1980)中國制定的一個編碼標準,如果有玩過小霸王學習機的話,應該會記得有個叫做「區位」的輸入法(沒記錯的話是按F4選擇)。就是打四個數字然後就出來漢字了,什麼原理呢。請看下面的區位碼錶,每一個字元都有對應一個編號。其中前兩位為「區」,後兩位為「位」,中文漢字的編號區號是從16開始的,位號從1開始。前面的區號有一些符號、數字、字母、注音符號(台)、製表符、日文等等。

而GB2312編碼就是基於區位碼的,用雙位元組編碼表示中文和中文符號。一般編碼方式是:0xA0+區號,0xA0+位號。如下表中的 「安」,區位號是1618(十進位),那麼「安」字的GB2312編碼就是 0xA0+16 0xA0+18 也就是 0xB0 0xB2 。根據區位碼錶,GB2312的漢字編碼範圍是0xB0A1~0xF7FE

可能大家注意到了,區位碼里有英文和數字,按道理說是不是也應該是雙位元組的呢。而一般情況下,我們見到的英文和數字是單位元組的,以ASCII編碼,也就是說現代的GBK編碼是兼容ASCII編碼的。比如一個數字2,對應的二進位是0x32,而不是 0xA3 0xB2。那麼問題來了,0xA3 0xB2 又對應到什麼呢?還是2(笑)。注意看了,這裡的2跟2是不是有點不太一樣?!確實是不一樣的。這裡的雙位元組2是全形的二,ASCII的2是半形的二,一般輸入法里的切換全形半形就是這裡不同。

如果留意過早些年的手機(功能機),會發現人名中常見的「燊」字是打不出來的。為什麼呢?因為早期的區位碼錶裡面並沒有這些字,也就是說早期的GB2312也是沒有這些字的。到後來的GBK(1995)才補充了大量的漢字進去,當然現在的安卓蘋果應該都是GBK字型檔了。再看看這些補充的漢字的位元組碼 燊 0x9F 0xF6 。和前面說到的GB2312不同,有的字的編碼比 0xA0 0xA0 還小,難道新補充的區位號還能是負的??其實不然,這次的補充只補充了計算機編碼表,並沒有補充區位碼錶。也就是說區位碼錶並沒有更新,用區位碼打字法還是打不出這些字,而網上的反向區位碼錶查詢也只是按照GBK的編碼計算,並不代表字與區位號完全對應。時代的發展,區位碼錶早已經是進入博物館的東西了。

Big5是與GB2312同時期的一種台灣地區繁體字的編碼格式。後來GBK編碼的制定,把Big5用的繁體字也包含進來(但編碼不兼容),還增加了一些其它的中文字元。細心的朋友可能還會發現,台灣香港用的繁體字(如KTV里的字幕)跟大陸用的繁體字還有點筆畫上的不一樣,其實這跟編碼無關,是字體的不同,大陸一般用的是宋體楷體黑體,港澳台常用的是明體(鳥哥Linux私房菜用的是新細明體)。GBK總體編碼範圍為0x8140~0xFEFE,首位元組在 0x81~0xFE 之間,尾位元組在 0x40~0xFE 之間,剔除 xx7F 一條線。詳細編碼表可以參考這個列表。微軟Windows安排給GBK的code page(代碼頁)是CP936,所以有時候看到編碼格式是CP936,其實就是GBK的意思。2000年和2005年,國家又先後兩次發布了GB18030編碼標準,兼容GBK,新增四位元組的編碼,但比較少見。

同一個編碼文件里,怎麼區分ASCII和中文編碼呢?從ASCII表我們知道標準ASCII只有128個字元,0~127即0x00~0x7F(0111 1111)。所以區分的方法就是,高位元組的最高位為0則為ASCII,為1則為中文。

UTF8編碼 與 Unicode編碼

GBK是中國標準,只在中國使用,並沒有表示大多數其它國家的編碼;而各國又陸續推出各自的編碼標準,互不兼容,非常不利於全球化發展。於是後來國際組織發行了一個全球統一編碼表,把全球各國文字都統一在一個編碼標準里,名為Unicode。很多人都很疑惑,到底UTF8與Unicode兩者有什麼關係?如果要類比的話,UTF8相當於GB2312,Unicode相當於區位碼錶,不同的是它們之間的編號範圍和轉換公式。那什麼是原始的Unicode編碼呢?如果你用過PHP的話,json_encode函數默認會把中文編碼成為Unicode,比如「首發於博客園」就會轉碼成「u9996u53d1u4e8eu535au5ba2u56ed」。可以看到每個字都變成了 uXXXX的形式,這個就是文字的對應Unicode編碼,u表示Unicode的意思,現行的Unicode編碼標準里,絕大多數程序語言只支持雙位元組。英文字母、標點也收納在Unicode編碼中。有興趣的可以在站長工具里嘗試「中文轉Unicode」,可以得到你輸入文字的Unicode編碼。

因為英文字元也全部使用雙位元組,存儲成本和流量會大大地增加,所以Unicode編碼大多數情況並沒有被原始地使用,而是被轉換編碼成UTF8。下表就是其轉換公式:

第一種:Unicode從 0x0000 到 0x007F 範圍的,是不是有點熟悉?對,其實就是標準ASCII碼裡面的內容,所以直接去掉前面那個位元組 0x00,使用其第二個位元組(與ASCII碼相同)作為其編碼,即為單位元組UTF8。

第二種:Unicode從 0x0080 到 0x07FF 範圍的,轉換成雙位元組UTF8。

第三種:Unicode從 0x8000 到 0xFFFF 範圍的,轉換成三位元組UTF8,一般中文都是在這個範圍里。

第四種:超過雙位元組的Unicode目前還沒有廣泛支持,不做討論。

例如「博」字的Unicode編碼是u535a。0x535A在0x0800~0xFFFF之間,所以用3位元組模板 1110yyyy 10yyyyxx 10xxxxxx。將535A寫成二進位是:0101 0011 0101 1010,高八位分別代替y,低八位分別代替x,得到 11100101 10001101 10011010,也就是 0xE58D9A ,這就是博字的UTF8編碼。

前面提到,GBK的編碼里英文字元有全形和半形之分,全形為GBK的標準編碼過的雙位元組2,半形為ASCII的單位元組2。那現在UTF8是全部用一個公式,理論上只有半形的2的,怎麼支持全形的2呢?哈哈,結果是Unicode為中國特色的全形英文字元也單獨分配了編碼,簡單粗暴。比如全形的2的Unicode編碼是 uFF12,轉換到UTF8就是 0xEFBC92。

文章開頭有說到 UCS-2,其實UCS-2就是原始的雙位元組Unicode編碼,用二進位編輯器打開UCS-2大端模式的文本文件,從左往右看,看到的就是每個字元的Unicode編碼了。至於什麼是大端小端,就是位元組的存放順序不同,這一般是嵌入式編程的範疇。

如何區分一個文本是無BOM的UTF8還是GBK

前面說到的幾種編碼,其中有的是有BOM頭的,可以直接根據BOM頭區分出其編碼。有兩個是沒有BOM頭的,UTF8和GBK,那麼兩者怎麼區分呢?答案是,只能按大量的編碼分析來區分。目前識別準確率很高的有:Notepad++等一些常用的IDE,PHP的mb_系列函數,python的chardet庫及其它語言衍生版如jchardet,jschardet 等(請自行github)。


推薦閱讀:

TAG:編碼 | UTF8 |