彻底搞懂编码 GBK 和 UTF8
常用編碼格式一覽
首先來(lái)看一下常用的編碼有哪些,截圖自Notepad++。其中ANSI在中國(guó)大陸即為GBK(以前是GB2312),最常用的是 GBK 和 UTF8無(wú)BOM 編碼格式。后面三個(gè)都是有BOM頭的文本格式,UCS-2即為人們常說(shuō)的Unicode編碼,又分為大端、小端。
所謂BOM頭(Byte Order Mark)就是文本文件中開(kāi)始的幾個(gè)并不表示任何字符的字節(jié),用二進(jìn)制編輯器(如bz.exe)就能看到了。
何為GBK,何為GB2312,與區(qū)位碼有何淵源?
區(qū)位碼是早些年(1980)中國(guó)制定的一個(gè)編碼標(biāo)準(zhǔn),如果有玩過(guò)小霸王學(xué)習(xí)機(jī)的話,應(yīng)該會(huì)記得有個(gè)叫做“區(qū)位”的輸入法(沒(méi)記錯(cuò)的話是按F4選擇)。就是打四個(gè)數(shù)字然后就出來(lái)漢字了,什么原理呢。請(qǐng)看下面的區(qū)位碼表,每一個(gè)字符都有對(duì)應(yīng)一個(gè)編號(hào)。其中前兩位為“區(qū)”,后兩位為“位”,中文漢字的編號(hào)區(qū)號(hào)是從16開(kāi)始的,位號(hào)從1開(kāi)始。前面的區(qū)號(hào)有一些符號(hào)、數(shù)字、字母、注音符號(hào)(臺(tái))、制表符、日文等等。
而GB2312編碼就是基于區(qū)位碼的,用雙字節(jié)編碼表示中文和中文符號(hào)。一般編碼方式是:0xA0+區(qū)號(hào),0xA0+位號(hào)。如下表中的 “安”,區(qū)位號(hào)是1618(十進(jìn)制),那么“安”字的GB2312編碼就是 0xA0+16? 0xA0+18 也就是 0xB0 0xB2 。根據(jù)區(qū)位碼表,GB2312的漢字編碼范圍是0xB0A1~0xF7FE
區(qū)位碼表節(jié)選
可能大家注意到了,區(qū)位碼里有英文和數(shù)字,按道理說(shuō)是不是也應(yīng)該是雙字節(jié)的呢。而一般情況下,我們見(jiàn)到的英文和數(shù)字是單字節(jié)的,以ASCII編碼,也就是說(shuō)現(xiàn)代的GBK編碼是兼容ASCII編碼的。比如一個(gè)數(shù)字2,對(duì)應(yīng)的二進(jìn)制是0x32,而不是 0xA3 0xB2。那么問(wèn)題來(lái)了,0xA3 0xB2 又對(duì)應(yīng)到什么呢?還是2(笑)。注意看了,這里的2跟2是不是有點(diǎn)不太一樣?!確實(shí)是不一樣的。這里的雙字節(jié)2是全角的二,ASCII的2是半角的二,一般輸入法里的切換全角半角就是這里不同。
如果留意過(guò)早些年的手機(jī)(功能機(jī)),會(huì)發(fā)現(xiàn)人名中常見(jiàn)的“燊”字是打不出來(lái)的。為什么呢?因?yàn)樵缙诘膮^(qū)位碼表里面并沒(méi)有這些字,也就是說(shuō)早期的GB2312也是沒(méi)有這些字的。到后來(lái)的GBK(1995)才補(bǔ)充了大量的漢字進(jìn)去,當(dāng)然現(xiàn)在的安卓蘋果應(yīng)該都是GBK字庫(kù)了。再看看這些補(bǔ)充的漢字的字節(jié)碼 燊 0x9F 0xF6?。和前面說(shuō)到的GB2312不同,有的字的編碼比 0xA0 0xA0 還小,難道新補(bǔ)充的區(qū)位號(hào)還能是負(fù)的??其實(shí)不然,這次的補(bǔ)充只補(bǔ)充了計(jì)算機(jī)編碼表,并沒(méi)有補(bǔ)充區(qū)位碼表。也就是說(shuō)區(qū)位碼表并沒(méi)有更新,用區(qū)位碼打字法還是打不出這些字,而網(wǎng)上的反向區(qū)位碼表查詢也只是按照GBK的編碼計(jì)算,并不代表字與區(qū)位號(hào)完全對(duì)應(yīng)。時(shí)代的發(fā)展,區(qū)位碼表早已經(jīng)是進(jìn)入博物館的東西了。
Big5是與GB2312同時(shí)期的一種臺(tái)灣地區(qū)繁體字的編碼格式。后來(lái)GBK編碼的制定,把Big5用的繁體字也包含進(jìn)來(lái)(但編碼不兼容),還增加了一些其它的中文字符。細(xì)心的朋友可能還會(huì)發(fā)現(xiàn),臺(tái)灣香港用的繁體字(如KTV里的字幕)跟大陸用的繁體字還有點(diǎn)筆畫上的不一樣,其實(shí)這跟編碼無(wú)關(guān),是字體的不同,大陸一般用的是宋體楷體黑體,港澳臺(tái)常用的是明體(鳥哥Linux私房菜用的是新細(xì)明體)。GBK總體編碼范圍為0x8140~0xFEFE,首字節(jié)在 0x81~0xFE 之間,尾字節(jié)在 0x40~0xFE 之間,剔除 xx7F 一條線。詳細(xì)編碼表可以參考這個(gè)列表。微軟Windows安排給GBK的code page(代碼頁(yè))是CP936,所以有時(shí)候看到編碼格式是CP936,其實(shí)就是GBK的意思。2000年和2005年,國(guó)家又先后兩次發(fā)布了GB18030編碼標(biāo)準(zhǔn),兼容GBK,新增四字節(jié)的編碼,但比較少見(jiàn)。
同一個(gè)編碼文件里,怎么區(qū)分ASCII和中文編碼呢?從ASCII表我們知道標(biāo)準(zhǔn)ASCII只有128個(gè)字符,0~127即0x00~0x7F(0111 1111)。所以區(qū)分的方法就是,高字節(jié)的最高位為0則為ASCII,為1則為中文。
UTF8編碼 與 Unicode編碼
GBK是中國(guó)標(biāo)準(zhǔn),只在中國(guó)使用,并沒(méi)有表示大多數(shù)其它國(guó)家的編碼;而各國(guó)又陸續(xù)推出各自的編碼標(biāo)準(zhǔn),互不兼容,非常不利于全球化發(fā)展。于是后來(lái)國(guó)際組織發(fā)行了一個(gè)全球統(tǒng)一編碼表,把全球各國(guó)文字都統(tǒng)一在一個(gè)編碼標(biāo)準(zhǔn)里,名為Unicode。很多人都很疑惑,到底UTF8與Unicode兩者有什么關(guān)系?如果要類比的話,UTF8相當(dāng)于GB2312,Unicode相當(dāng)于區(qū)位碼表,不同的是它們之間的編號(hào)范圍和轉(zhuǎn)換公式。那什么是原始的Unicode編碼呢?如果你用過(guò)PHP的話,json_encode函數(shù)默認(rèn)會(huì)把中文編碼成為Unicode,比如“首發(fā)于博客園”就會(huì)轉(zhuǎn)碼成“\u9996\u53d1\u4e8e\u535a\u5ba2\u56ed”??梢钥吹矫總€(gè)字都變成了 \uXXXX 的形式,這個(gè)就是文字的對(duì)應(yīng)Unicode編碼,\u表示Unicode的意思,網(wǎng)上也有用U+表示unicode?,F(xiàn)行的Unicode編碼標(biāo)準(zhǔn)里,絕大多數(shù)程序語(yǔ)言只支持雙字節(jié)。英文字母、標(biāo)點(diǎn)也收納在Unicode編碼中。有興趣的可以在站長(zhǎng)工具里嘗試“中文轉(zhuǎn)Unicode”,可以得到你輸入文字的Unicode編碼。
因?yàn)橛⑽淖址踩渴褂秒p字節(jié),存儲(chǔ)成本和流量會(huì)大大地增加,所以Unicode編碼大多數(shù)情況并沒(méi)有被原始地使用,而是被轉(zhuǎn)換編碼成UTF8。下表就是其轉(zhuǎn)換公式:
第一種:Unicode從 0x0000 到 0x007F 范圍的,是不是有點(diǎn)熟悉?對(duì),其實(shí)就是標(biāo)準(zhǔn)ASCII碼里面的內(nèi)容,所以直接去掉前面那個(gè)字節(jié) 0x00,使用其第二個(gè)字節(jié)(與ASCII碼相同)作為其編碼,即為單字節(jié)UTF8。
第二種:Unicode從 0x0080 到 0x07FF 范圍的,轉(zhuǎn)換成雙字節(jié)UTF8。
第三種:Unicode從 0x8000 到 0xFFFF 范圍的,轉(zhuǎn)換成三字節(jié)UTF8,一般中文都是在這個(gè)范圍里。
第四種:超過(guò)雙字節(jié)的Unicode目前還沒(méi)有廣泛支持,僅見(jiàn)emoji表情在此范圍。
例如“博”字的Unicode編碼是\u535a。0x535A在0x0800~0xFFFF之間,所以用3字節(jié)模板 1110yyyy 10yyyyxx 10xxxxxx。將535A寫成二進(jìn)制是:0101 0011 0101 1010,高八位分別代替y,低八位分別代替x,得到 11100101 10001101 10011010,也就是 0xE58D9A ,這就是博字的UTF8編碼。
前面提到,GBK的編碼里英文字符有全角和半角之分,全角為GBK的標(biāo)準(zhǔn)編碼過(guò)的雙字節(jié)2,半角為ASCII的單字節(jié)2。那現(xiàn)在UTF8是全部用一個(gè)公式,理論上只有半角的2的,怎么支持全角的2呢?哈哈,結(jié)果是Unicode為中國(guó)特色的全角英文字符也單獨(dú)分配了編碼,簡(jiǎn)單粗暴。比如全角的2的Unicode編碼是 \uFF12,轉(zhuǎn)換到UTF8就是 0xEFBC92。
文章開(kāi)頭有說(shuō)到 UCS-2,其實(shí)UCS-2就是原始的雙字節(jié)Unicode編碼,用二進(jìn)制編輯器打開(kāi)UCS-2大端模式的文本文件,從左往右看,看到的就是每個(gè)字符的Unicode編碼了。至于什么是大端小端,就是字節(jié)的存放順序不同,這一般是嵌入式編程的范疇。
如何區(qū)分一個(gè)文本是無(wú)BOM的UTF8還是GBK
前面說(shuō)到的幾種編碼,其中有的是有BOM頭的,可以直接根據(jù)BOM頭區(qū)分出其編碼。有兩個(gè)是沒(méi)有BOM頭的,UTF8和GBK,那么兩者怎么區(qū)分呢?答案是,只能按大量的編碼分析來(lái)區(qū)分。目前識(shí)別準(zhǔn)確率很高的有:Notepad++等一些常用的IDE,PHP的mb_系列函數(shù),python的chardet庫(kù)及其它語(yǔ)言衍生版如jchardet,jschardet 等(請(qǐng)自行g(shù)ithub)。
那么這些庫(kù)是怎么區(qū)分這些編碼的呢?那就是詞庫(kù),你會(huì)看到庫(kù)的源碼里有大量的數(shù)組,其實(shí)就是對(duì)應(yīng)一個(gè)編碼里的常見(jiàn)詞組編碼組合。同樣的文件字節(jié)流在一個(gè)詞組庫(kù)里的匹配程度越高,就越有可能是該編碼,判斷的準(zhǔn)確率就越大。而文件中的中文越少越零散,判斷的準(zhǔn)確率就越低。
關(guān)于ASCII
文中多次提及ASCII編碼,其實(shí)這應(yīng)該是每個(gè)程序員都非常熟悉、認(rèn)真了解的東西。對(duì)于嵌入式開(kāi)發(fā)的人來(lái)說(shuō),應(yīng)該能隨時(shí)在字符與ASCII碼中轉(zhuǎn)換,就像十六進(jìn)制與二進(jìn)制之間的轉(zhuǎn)換一樣。標(biāo)準(zhǔn)ASCII是128個(gè),范圍是0x00~0x7F (0000 0000~0111 0000) ,最高位為0。也有一個(gè)擴(kuò)展ASCII碼規(guī)則,把最高位也用上了,變成256個(gè),但是這個(gè)擴(kuò)展標(biāo)準(zhǔn)爭(zhēng)議很大,沒(méi)有得到推廣,應(yīng)該以后不會(huì)得到推廣。因?yàn)闊o(wú)論是GBK還是UTF8,如果ASCII字符編碼最高位能為1都會(huì)造成混亂無(wú)法解析。
以GBK為例,如果ASCII的字符最高位也能是1,那么是應(yīng)該截取一個(gè)解析為ASCII呢?還是截取兩個(gè)解析為中文字符?這根本無(wú)法判斷。UTF8也是同理,遇到 0xxx 開(kāi)頭則截取一個(gè)(即為標(biāo)準(zhǔn)ASCII), 遇到 110x 開(kāi)頭則截取兩個(gè),遇到 1110 開(kāi)頭則截取三個(gè),如果ASCII包含1開(kāi)頭的,則無(wú)法確定何時(shí)截取多少個(gè)。
在哪里還能一睹擴(kuò)展ASCII的真容呢?其實(shí)很簡(jiǎn)單,只要把網(wǎng)頁(yè)的meta改成ASCII就行了 <meta charset="ASCII" />?。又或者瀏覽器的編碼選擇“西方”,即可見(jiàn)到與平常所見(jiàn)不同的亂碼。(截圖為火狐)
?
姊妹篇:《emoji表情 與 iconfont 一鍋燉?》
總結(jié)
以上是生活随笔為你收集整理的彻底搞懂编码 GBK 和 UTF8的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 排序算法的稳定性及其汇总
- 下一篇: m35c android 4.4,透明带