現(xiàn)在Unicode已然一統(tǒng)天下,我想很多年輕的程序員可能都沒(méi)遇到過(guò)編碼問(wèn)題,更不用說(shuō)了解編碼的發(fā)展了。前些日子在一個(gè)老網(wǎng)站上偶遇亂碼,雖然入行時(shí)間不短,但對(duì)其究竟也是不甚了解,好奇心驅(qū)使下落入深坑。還好經(jīng)過(guò)一段時(shí)間的摸爬滾打,邊學(xué)邊寫(xiě),總算大概理清了個(gè)脈絡(luò),記錄之,分享之。
概念
字符是一個(gè)信息單位,在計(jì)算機(jī)里面,一個(gè)中文漢字是一個(gè)字符,一個(gè)英文字母是一個(gè)字符,一個(gè)阿拉伯?dāng)?shù)字是一個(gè)字符,一個(gè)標(biāo)點(diǎn)符號(hào)也是一個(gè)字符。
字符集是字符組成的集合,通常以二維表的形式存在,二維表的內(nèi)容和大小是由使用者的語(yǔ)言而定,是英語(yǔ),是漢語(yǔ),還是阿拉伯語(yǔ)。
字符編碼是把字符集中的字符編碼為特定的二進(jìn)制數(shù),以便在計(jì)算機(jī)中存儲(chǔ)。編碼方式一般就是對(duì)二維表的橫縱坐標(biāo)進(jìn)行變換的算法。一般都比較簡(jiǎn)單,直接把橫縱坐標(biāo)拼一起就完事了。后來(lái)隨著字符集的不斷擴(kuò)大,為了節(jié)省存儲(chǔ)空間,才出現(xiàn)了各種各樣的算法。
字符集和字符編碼一般都是成對(duì)出現(xiàn)的,如ASCII、IOS-8859-1、GB2312、GBK,都是即表示了字符集又表示了對(duì)應(yīng)的字符編碼,以后統(tǒng)稱為編碼。Unicode比較特殊,后面細(xì)說(shuō)。
發(fā)展
單字節(jié)
計(jì)算機(jī)是美國(guó)人發(fā)明的,人家用的是美式英語(yǔ),字符比較少,所以一開(kāi)始就設(shè)計(jì)了一個(gè)不大的二維表,128個(gè)字符,取名叫ASCII(American Standard Code for Information Interchange)。128個(gè)碼位,用7位二進(jìn)制數(shù)表示,由于計(jì)算機(jī)1個(gè)字節(jié)是8位二進(jìn)制數(shù),所以最高位為0,即00000000-01111111
或0x00-0x7F
。
后來(lái)美國(guó)人發(fā)現(xiàn)128個(gè)碼位不夠用,于是在原來(lái)二維表的基礎(chǔ)上進(jìn)行了擴(kuò)展,256個(gè)字符,取名叫EASCII(Extended ASCII)。256個(gè)碼位,用8位二進(jìn)制數(shù)表示,即00000000-11111111
或0x00-0xFF
。
當(dāng)計(jì)算機(jī)傳到了歐洲,美國(guó)人的標(biāo)準(zhǔn)不適用了,但是改改還能湊合。于是國(guó)際標(biāo)準(zhǔn)化組織在ASCII的基礎(chǔ)上進(jìn)行了擴(kuò)展,形成了ISO-8859標(biāo)準(zhǔn),跟EASCII類似,兼容ASCII,在高128個(gè)碼位上有所區(qū)別。但是由于歐洲的語(yǔ)言環(huán)境十分復(fù)雜,所以根據(jù)各地區(qū)的語(yǔ)言又形成了很多子標(biāo)準(zhǔn),ISO-8859-1、ISO-8859-2、ISO-8859-3、……、ISO-8859-16,真是令人發(fā)指。
雙字節(jié)
當(dāng)計(jì)算機(jī)傳到了亞洲,尤其是東亞,國(guó)際標(biāo)準(zhǔn)被秒殺了,路邊小孩隨便說(shuō)句話,256個(gè)碼位就不夠用了。于是乎繼續(xù)擴(kuò)大二維表,單字節(jié)改雙字節(jié),16位二進(jìn)制數(shù),65536個(gè)碼位。在不同國(guó)家和地區(qū)又出現(xiàn)了很多編碼,大陸的GB2312、港臺(tái)的BIG5、日本的Shift JIS等等。
注意65536個(gè)碼位這種說(shuō)法只是理想情況,由于雙字節(jié)編碼可以是變長(zhǎng)的,也就是說(shuō)同一個(gè)編碼里面有些字符是單字節(jié)表示,有些字符是雙字節(jié)表示。這樣做的好處是,一方面可以兼容ASCII,另一方面可以節(jié)省存儲(chǔ)容量,代價(jià)就是會(huì)損失一部分碼位。而且編碼的設(shè)計(jì)也并不是想象的那樣,所有字符從頭到尾布滿整個(gè)二維表,都是有預(yù)留空間的。比如說(shuō)GBK是GB2312的擴(kuò)展(K竟然是拼音KuoZhan的縮寫(xiě)),按理說(shuō)都屬于雙字節(jié)編碼,碼位是一樣的,根本談不上擴(kuò)展,但實(shí)際上是預(yù)留空間在起作用。比如下圖為GBK的編碼空間,GBK/1、GBK/2是GB2312的區(qū)域,GBK/3、GBK/4、GBK/5是GBK的區(qū)域,紅色是用戶自定義區(qū)域,白色可能就是由于變長(zhǎng)編碼損失的區(qū)域了。
Unicode
當(dāng)互聯(lián)網(wǎng)席卷了全球,地域限制被打破了,不同國(guó)家和地區(qū)的計(jì)算機(jī)在交換數(shù)據(jù)的過(guò)程中,就會(huì)出現(xiàn)亂碼的問(wèn)題,跟語(yǔ)言上的地理隔離差不多。亂碼是怎么出現(xiàn)的呢?對(duì)同一組二進(jìn)制數(shù)據(jù),不同的編碼會(huì)解析出不同的字符,用對(duì)了編碼,解析出來(lái)的字符組成的文字是有意義的,用錯(cuò)了編碼,解析出來(lái)的字符組成的文字是沒(méi)意義的,也就是通常所說(shuō)的亂碼。
經(jīng)過(guò)之前的介紹,編碼很多,全球的計(jì)算機(jī)們沒(méi)辦法在一起好好的玩耍。要徹底解決這個(gè)問(wèn)題,替代原先基于語(yǔ)言的編碼系統(tǒng),就需要一個(gè)通用的字符集UCS(Universal Character Set)和一個(gè)通用的字符編碼Unicode。一開(kāi)始UCS用2個(gè)字節(jié)表示,叫做UCS-2,后來(lái)2個(gè)字節(jié)不夠用,于是就用4個(gè)字節(jié),叫做UCS-4。但是如果每一個(gè)字符都用4個(gè)字節(jié)來(lái)表示的話,相較之前的編碼會(huì)浪費(fèi)很多存儲(chǔ)空間,尤其是相對(duì)ASCII等單字節(jié)編碼會(huì)非常吃虧。并且當(dāng)時(shí)已經(jīng)有些廠商在雙字節(jié)編碼上投入了很大的精力。于是UTF-16就被作為一種折中的方案提了出來(lái),既保持了兩字節(jié)不變,又保證了足夠的編碼空間。而UTF-32是與UCS-4相對(duì)應(yīng)的,UTF-8則由于擴(kuò)展性比較強(qiáng),從容應(yīng)對(duì)了UCS-2到UCS-4的改變。關(guān)于各種UTF的實(shí)現(xiàn)細(xì)節(jié)可以點(diǎn)擊鏈接查看(翻墻),已經(jīng)說(shuō)得很清楚了,就不贅述了,但不得不提一下,UTF-16的設(shè)計(jì)還挺巧妙的。
UTF(Unicode Transformation Format)是將Unicode編碼進(jìn)行了轉(zhuǎn)換,通常會(huì)在存儲(chǔ)空間和效率上進(jìn)行一定的權(quán)衡,有很多種實(shí)現(xiàn)方式,前面提到了UTF-8和UTF-16是最常用的。這就是之前提到的Unicode的特殊之處。
歷史
ASCII
1960 開(kāi)發(fā)
1963 發(fā)布
1986 最后一次更新ISO-8859-1
1998 發(fā)布GB2312
1980 發(fā)布GBK
1993 發(fā)布UCS-2
In the late 1980sUnicode
1987 開(kāi)發(fā)
1991 發(fā)布
1996 實(shí)現(xiàn)代理機(jī)制(UTF-16)
2015 最新版8.0UTF-8
1993 發(fā)布
2008 流行UTF-16
1996 開(kāi)發(fā)
2000 發(fā)布
根據(jù)以上各個(gè)編碼發(fā)展的一些時(shí)間節(jié)點(diǎn),再配合下圖UTF-8制霸互聯(lián)網(wǎng)過(guò)程,會(huì)有一個(gè)比較清晰的了解。
尾聲
雖然Unicode解決了地球上的問(wèn)題,但是以后三體人入侵可怎么辦,根據(jù)這些天研究編碼發(fā)展歷史來(lái)看,比較靠譜的回答——還是到時(shí)再說(shuō)吧。
本文是根據(jù)互聯(lián)網(wǎng)上各種信息來(lái)源,主要是維基百科,加上自己的理解,進(jìn)行的總結(jié)和演繹,肯定有不準(zhǔn)確或錯(cuò)誤的地方,還望不吝賜教。