【編碼】GB編碼的一些零碎
前文提到,ISO646規(guī)定的7-bit ASCII標(biāo)準(zhǔn)并不能適應(yīng)所有語(yǔ)言,在當(dāng)時(shí)那個(gè)條件下,標(biāo)準(zhǔn)提供了若干可替代字符的變種方案,例如上篇說(shuō)到的德文編碼DIN。然而這是一個(gè)有損的方案,典型表現(xiàn)之一就是催化出了C語(yǔ)言代用記號(hào)這種東西,而且世界上的文字不止英文,即便只看歐洲的拉丁文字符集,替代字符的做法也只能算是揚(yáng)湯止沸
幸好就ASCII來(lái)說(shuō),7-bit的范圍已經(jīng)足夠大了,而一個(gè)字節(jié)是8-bit,這就給其他語(yǔ)言文字留了設(shè)計(jì)思路的空間。在unicode普及之前,甚至unicode發(fā)布之前,如何用字節(jié)序列來(lái)表示一個(gè)超出128大小的字符序列就是各種編碼需要研究的問(wèn)題,當(dāng)然,如果能用多字節(jié)來(lái)表示一個(gè)字符,那這就不是一個(gè)問(wèn)題,實(shí)際工程問(wèn)題是需要兼容ASCII,且做到空間占用盡可能短
一個(gè)字節(jié)8-bit進(jìn)入ISO的時(shí)間相對(duì)較晚(印象中是2008年),但在此之前已經(jīng)在很長(zhǎng)時(shí)間里成為主流實(shí)現(xiàn)了
很多人都是win系統(tǒng)入門(mén)計(jì)算機(jī)的,所有編碼相關(guān)的知識(shí)很多也來(lái)自于中文windows
最早學(xué)計(jì)算機(jī)的時(shí)候,我接觸到的中文編碼是GB2312,其中GB是國(guó)標(biāo)的意思,意即國(guó)家標(biāo)準(zhǔn)的中文編碼,由此衍生的還有輸入法中的區(qū)位碼(大概很多人都沒(méi)聽(tīng)過(guò)了)
GB2312兼容ASCII,采用兩個(gè)值>=128的字節(jié)表示一個(gè)漢字,也就是說(shuō),它是一個(gè)變長(zhǎng)編碼,解析原則是:
如果一個(gè)字節(jié)在0~127,那么表示一個(gè)普通ASCII字符
如果一個(gè)字節(jié)在128~255,那么繼續(xù)讀下一個(gè)字符(且下一個(gè)字符是128~255),兩個(gè)字符聯(lián)合起來(lái)的值代表一個(gè)漢字(也包括拉丁字母、日文、標(biāo)點(diǎn)符號(hào)的全角字符等,這些字符數(shù)量和漢字比起來(lái)是九牛一毛,就囊括了)
GB2312包含了7573個(gè)字符,用下面的代碼很容易測(cè)試出來(lái)(Python3):
除去兼容的ASCII以及上面提及的全角字符外,漢字大約收錄了6000多個(gè)
這個(gè)數(shù)量,熟悉漢字的中國(guó)人一看就知道不行,最簡(jiǎn)單的,當(dāng)年高考錄入名字的時(shí)候,很多冷僻字都沒(méi)法輸入
所以后面擴(kuò)展了標(biāo)準(zhǔn),推出了GBK,這里的K是指擴(kuò)展的意思,GBK將字符數(shù)擴(kuò)充至21919個(gè)字符,也成了當(dāng)年windows下中文的標(biāo)準(zhǔn)字符編碼,又名cp936,cp是code page的意思,相當(dāng)于GBK在編碼規(guī)范中的文件編號(hào)
以前很多人將GBK稱為ANSI編碼,在python中用ANSI也的確能encode,而且在中文win下測(cè)試,字符集和GBK差不太多,不過(guò)ANSI是美國(guó)國(guó)家標(biāo)準(zhǔn)委員會(huì)的意思,并不是直接指代GBK,或者說(shuō)它和系統(tǒng)是相關(guān)的編碼集,且字符集還是有差異的,所以“ANSI編碼”這個(gè)說(shuō)法并不適合
GBK在編碼上沿襲了GB2312的做法,兼容ASCII,用雙字節(jié)表示中文,但是從字符數(shù)量我們很容易看出,它不可能遵循“雙字節(jié)都是128~255”這一規(guī)范,因?yàn)檫@個(gè)范圍只有128,雙字節(jié)能表示的則是128×128=16384個(gè)字符,不可能在20000+的數(shù)量
所以,既要兼容ASCII,又要限定非ASCII字符是雙字節(jié),那只能在第二個(gè)字節(jié)做妥協(xié)了,也就是說(shuō),GBK中的相當(dāng)多的非ASCII字符,是第一個(gè)字符128~255,而第二個(gè)字符是0~127的,而我們知道很多文本處理是把文件當(dāng)字節(jié)序列來(lái)看待,這就可能出現(xiàn)一些問(wèn)題
例如我在騰訊碰到過(guò)的一個(gè)日志處理問(wèn)題(因?yàn)闅v史原因,某些老系統(tǒng)用的是GBK編碼):
這是一個(gè)用豎線分隔字段的日志文件,我們用Python2來(lái)解析:
這里出現(xiàn)了異常,原因是:
“珅”這個(gè)字在GBK編碼中,第二個(gè)字符是豎線,干擾了日志格式
當(dāng)然,雙字節(jié)的第二個(gè)字節(jié)是0~127的,一般也都是比較生僻的漢字,但它們很多時(shí)候會(huì)出現(xiàn)在姓名中,在文本處理上會(huì)出現(xiàn)麻煩
要避免類似的問(wèn)題,統(tǒng)一用utf8就好了,為什么utf8可以規(guī)避類似問(wèn)題呢,看看utf8的編碼規(guī)定就知道了:

可以看到,作為一種多字節(jié)變長(zhǎng)編碼,utf8通過(guò)每個(gè)字節(jié)的前綴bit嚴(yán)格區(qū)分了它們的作用:
高位是0的,為單字節(jié)的ASCII字符
高位10的,是多字節(jié)序列中的后續(xù)字節(jié)
高位是11開(kāi)頭的,是多字節(jié)序列的首字節(jié)
這些規(guī)定不但使得不同字符的編碼字節(jié)不會(huì)互相混淆,也確定了多字節(jié)序列的邊界,抗亂碼能力也非常強(qiáng),丟失單字節(jié)是不會(huì)造成亂碼的,很容易糾錯(cuò),只有丟失了連續(xù)多個(gè)字節(jié)的時(shí)候,才可能出現(xiàn)錯(cuò)誤內(nèi)容