我們最初學(xué)習(xí)計(jì)算機(jī)的時(shí)候,都學(xué)過ASCII編碼。但是為了表示各種各樣的語言,在計(jì)算機(jī)技術(shù)的發(fā)展過程中,逐漸出現(xiàn)了很多不同標(biāo)準(zhǔn)的編碼格式,重要的有Unicode、UTF、ISO 8859-1和中國人經(jīng)常使用的GB2312、BIG5、GBK等。
計(jì)算機(jī)世界中最早的編碼應(yīng)數(shù) ANSI 的“ASCII”編碼(American Standard Code for Information Interchange,美國信息互換標(biāo)準(zhǔn)代碼)。ASCII是7位編碼,能表示128個(gè)字符。
后來計(jì)算機(jī)發(fā)展越來越廣泛,為了可以在計(jì)算機(jī)中保存更多的文字和符號(hào),啟用了從128到255的字符,被稱為”擴(kuò)展字符集”。這就是ISO 8859-1編碼,屬于單字節(jié)編碼,應(yīng)用于拉丁文系列。顯然,ISO 8859-1最多能表示的字符范圍是0-255(編碼范圍是0x00-0xFF),其中0x00-0x7F之間完全和ASCII一致,因此向下兼容ASCII。除ASCII收錄的字符外,ISO-8859-1收錄的字符還包括西歐語言、希臘語、泰語、阿拉伯語、希伯來語對(duì)應(yīng)的文字符號(hào)。歐元符號(hào)等出現(xiàn)的比較晚,沒有被收錄在ISO 8859-1當(dāng)中。
很明顯,ISO 8859-1編碼表示的字符范圍很窄,例如無法表示中文字符。但是由于ISO-8859-1編碼范圍使用了單字節(jié)內(nèi)的所有空間,在支持ISO 8859-1的系統(tǒng)中傳輸和存儲(chǔ)其他任何編碼的字節(jié)流都不會(huì)被拋棄。換言之,把其他任何編碼的字節(jié)流當(dāng)作ISO-8859-1編碼看待都沒有問題。這是個(gè)很重要的特性,所以很多情況下(如很多協(xié)議傳輸數(shù)據(jù)時(shí))都使用ISO 8859-1編碼。我們可以這么說,ASCII編碼是一個(gè)7位的容器,ISO 8859-1編碼是一個(gè)8位的容器。
比如,雖然“中文”兩個(gè)字符就不存在ISO 8859-1編碼,但可以用iso8859-1編碼來“表示”。通過查詢下文將要介紹的GB2312編碼表,“中文”應(yīng)該是"d6d0 cec4"兩個(gè)字符,使用ISO 8859-1編碼來“表示”的時(shí)候則將它拆開為4個(gè)字節(jié)來表示,即"d6 d0 ce c4"(事實(shí)上,在進(jìn)行存儲(chǔ)的時(shí)候,也是以字節(jié)為單位處理的)。如果使用Unicode編碼,則表示為"4e2d 6587";使用UTF編碼,則是6個(gè)字節(jié)"e4 b8 ad e6 96 87"。很明顯,這種使用ISO 8869-1對(duì)漢字進(jìn)行表示的方法還需要以另一種編碼為基礎(chǔ)。
有些環(huán)境下,將ISO 8859-1寫作Latin-1。
中國的常用漢字有6000多個(gè),如何保存呢?中華人民共和國政府制定GB2312標(biāo)準(zhǔn)是把ISO 8859-1編碼中那些127號(hào)之后的奇異符號(hào)們直接取消掉,規(guī)定:一個(gè)小于127的字符的意義與原來相同,但兩個(gè)大于127的字符連在一起時(shí),就表示一個(gè)漢字,前面的一個(gè)字節(jié)(高字節(jié))從0xA1用到 0xF7,后面一個(gè)字節(jié)(低字節(jié))從0xA1到0xFE,這樣我們就可以組合出大約7000多個(gè)簡體漢字了。在GB2312編碼里,還把數(shù)學(xué)符號(hào)、羅馬希臘的字母、日文的假名們都編進(jìn)去了,連在 ASCII 里本來就有的數(shù)字、標(biāo)點(diǎn)、字母都統(tǒng)統(tǒng)重新編了兩個(gè)字節(jié)長的編碼,這就是常說的”全角”字符,而原來在127號(hào)以下的那些就叫”半角”字符了。
GB2312是漢字的國標(biāo)碼,也是簡體漢字編碼規(guī)范。其表示漢字時(shí)是雙字節(jié)編碼,但也兼容單字節(jié)的ASCII編碼(0-127),因此是變長編碼系統(tǒng)。與此對(duì)應(yīng)的還有中華民國政府制定的BIG5,是繁體漢字的編碼規(guī)范。所謂的繁體中文Windows,簡體中文Windows,指的就是采用BIG5和GB2312編碼格式的操作系統(tǒng)。這兩種編碼方式不兼容,如果使用一種編碼的文本閱讀器來讀另一種編碼的文本,就會(huì)出現(xiàn)亂碼。比如在簡體中文Windows上讀BIG5編碼的文件,就是亂碼,反之亦然。使用簡體瀏覽器瀏覽的時(shí)候,到了繁體中文網(wǎng)站,如果不改變碼制,也是亂碼。
但是中國的漢字實(shí)在太多了,GB2312,BIG5所包含的漢字?jǐn)?shù)量也不足,比如朱總理的名字中間那個(gè)字一般就打不出。于是又有了GBK大字符集,簡而言之就是將所有亞洲文字的雙字節(jié)字符,包括簡體中文,繁體中文,日語,韓語等,都使用一種格式編碼,這樣就能夠做到在所有的語言平臺(tái)上面兼容。GBK編碼是兼容GB2312編碼的,因此也是變長編碼(雙字節(jié))。但GBK編碼中的2字節(jié)表示的字符不再要求低字節(jié)一定是127號(hào)之后的內(nèi)碼,只要高字節(jié)是大于127就固定表示這是一個(gè)漢字的開始,不管低字節(jié)是不是擴(kuò)展字符集里的內(nèi)容。GBK大字符集包含的漢字?jǐn)?shù)量比GB2312和BIG5多的多了,目前足夠使用。
后來少數(shù)民族也要用電腦了,于是我們?cè)贁U(kuò)展,又加了幾千個(gè)新的少數(shù)民族的字,GBK 擴(kuò)成了 GB18030。GB 18030 與 GB 2312-1980 和 GBK 都兼容,是一二四字節(jié)變長編碼系統(tǒng),也是目前最新最全的內(nèi)碼字集。
Unicode是Unicode.org制定的編碼標(biāo)準(zhǔn),目前得到了絕大部分操作系統(tǒng)和編程語言的支持。Unicode.org官方對(duì)Unicode的定義是:Unicode provides a unique number for every character??梢?,Unicode所做的是為每個(gè)字符定義了一個(gè)相應(yīng)的數(shù)字表示。比如,"a"的Unicode值是0x0061,“一”的Unicde值是0x4E00,這是最簡單的情況,每個(gè)字符用2個(gè)字節(jié)表示。
Unicode是最統(tǒng)一的編碼,可以用來表示所有語言的字符,而且是定長雙字節(jié)(如果考慮輔助平面,也有四字節(jié)的)編碼,包括英文字母在內(nèi),都以雙字節(jié)表示,所以它是不兼容ISO 8859-1編碼的。不過,相對(duì)于ISO 8859-1中所編碼的字符來說,Unicode編碼只是在前面增加了一個(gè)全0字節(jié),例如字母a的Unicode編碼為"00 61"。和GB2312/GBK等非定長編碼相比,定長編碼便于計(jì)算機(jī)處理,而Unicode又可以用來表示所有字符,所以在很多軟件內(nèi)部是使用Unicode編碼來處理的,比如java。
Unicode的編碼空間從U+0000到U+10FFFF,共有1,112,064個(gè)碼位(code point)可用來映射字符. Unicode的編碼空間可以劃分為17個(gè)平面(plane),每個(gè)平面包含216(65,536)個(gè)碼位。17個(gè)平面的碼位可表示為從U+xx0000到U+xxFFFF, 其中xx表示十六進(jìn)制值從00(16) 到10(16),共計(jì)17個(gè)平面。第一個(gè)平面稱為基本多語言平面(Basic Multilingual Plane, BMP),或稱第零平面(Plane 0),碼位從U+0000至U+FFFF,包含了最常用的字符。其他平面稱為輔助平面(Supplementary Planes)。
對(duì)于在Unicode基本多語言平面定義的字符(無論是拉丁字母、漢字或其他文字或符號(hào)),一律使用2字節(jié)儲(chǔ)存,但是從U+D800到U+DFFF之間的碼位區(qū)段是永久保留不映射到任何Unicode字符的。而在輔助平面定義的字符,即從U+010000到U+10FFFF的碼位,則(UTF-16的做法是)以代理對(duì)(surrogate pair)的形式,將其拆分成兩個(gè)2字節(jié)(位于0xD800-0xDFFF區(qū)段)共4字節(jié)的值來儲(chǔ)存。進(jìn)行代理對(duì)映射的方法本文就不深入討論了,有興趣的可以自行搜索。
考慮到Unicode編碼不兼容ISO 8859-1編碼,而且容易占用更多的空間:因?yàn)閷?duì)于英文字母,Unicode也需要兩個(gè)字節(jié)來表示,所以Unicode不便于傳輸和存儲(chǔ)。因此而產(chǎn)生了UTF編碼。
UTF 是 Unicode Translation Format,即把Unicode轉(zhuǎn)做某種格式的意思。事實(shí)上可以這么認(rèn)為,Unicode是一種編碼方式,和ACSII是同一個(gè)概念,而UTF是一種存儲(chǔ)方式(格式)。
那么,UTF是如何做這種格式轉(zhuǎn)換的呢?
UTF-32
Unicode.org定義了百萬個(gè)以上的字符,如果將所有的字符用統(tǒng)一的格式表示,需要的是4個(gè)字節(jié)。"a"的Unicode表示就會(huì)變成0x00000061,而“一”的Unicode值是0x00004E00。實(shí)際上,這就是UTF-32,也是Linux操作系統(tǒng)上所使用的Unicode方案,也是一種定長編碼。其缺點(diǎn)很顯然是造成了空間的巨大浪費(fèi),從而非常沒有效率,因此沒有UTF-8和UTF-16使用的頻繁。
UTF-16
但是,上文已經(jīng)提到,Unicode基本多語言平面的字符只使用2個(gè)字節(jié)就可以表示了,真正需要擴(kuò)展到4個(gè)字節(jié)來表示的字符少之又少。所以使用2個(gè)字節(jié)來表示Unicode代碼是一種很自然的選擇,例如英文的Unicode范圍是0x0000-0x007F,中文的Unicode范圍是0x4E00-0x9F**。對(duì)于那些擴(kuò)展平面中需要4個(gè)字節(jié)才能表示的字符,UTF-16使用一種代理的手法來擴(kuò)展(使用了基本多語言平面保留的0xD800-0xDFFF區(qū)段,表示這是一個(gè)代理,從而用2個(gè)16位碼元組成一個(gè)字符)。這樣的好處是大量的節(jié)約了存取空間,也提高了處理的速度。這種Unicode表示方法就是UTF-16,顯然,UTF-16需要1個(gè)或者2個(gè)16位長的碼元(也即2字節(jié)或4字節(jié))來表示,因此UTF-16是一個(gè)變長表示。一般在Windows平臺(tái)上,提到Unicode,那就是指UTF-16了。
UTF-16有一個(gè)著名的Endian的問題(名稱來自《格列夫游記》),即UTF16-LE和UTF16-BE,LE指Little Endian,而BE指Big Endian。關(guān)于這方面的信息,網(wǎng)上有很多相關(guān)的帖子。這與計(jì)算機(jī)的CPU架構(gòu)有一定關(guān)系,我們一般的X86系統(tǒng)都是Little Endian的,可以認(rèn)為UTF16就是UTF16-LE。
另外,UTF有一個(gè)BOM(Byte Order Mark)的問題。在Unicode編碼中有一個(gè)被稱為"Zero-Width No-Break Space (ZWNBSP)"的字符,它的編碼是0xFEFF。而0xFEFF在是一個(gè)實(shí)際中不存在的字符,所以不應(yīng)該出現(xiàn)在實(shí)際傳輸中。UCSUCS (Unicode Character Set) 規(guī)范建議我們?cè)趥鬏斪止?jié)流前,先傳輸字符"ZWNBSP"。這樣如果接收者收到FEFF,就表明這個(gè)字節(jié)流是Big-Endian的;如果收到FFFE,就表明這個(gè)字節(jié)流是Little- Endian的。因此字符"ZWNBSP"又被稱作BOM。
UTF-8
UTF-16的最大好處在于大部分字符都以固定長度的2字節(jié)儲(chǔ)存(如果考慮到Unicode輔助平面UTF-16也是變長編碼),但UTF-16卻無法兼容于ASCII編碼。由于對(duì)于歐洲和北美,實(shí)際上使用的編碼范圍在0x0000-0x00FF之間,只需要一個(gè)字符就可以表示所有的字符。即使是使用UTF16來作為內(nèi)存的存取方式,還是會(huì)帶來巨大的空間浪費(fèi),因此就有了UTF8的編碼方式。
UTF-8編碼是最靈活的UTF編碼形式,即兼容ISO 8859-1的編碼,同時(shí)也可以用來表示所有語言的字符。顯然,UTF-8編碼是變長編碼,每一個(gè)字符的長度從1-6個(gè)字節(jié)不等。另外,UTF編碼自帶簡單的校驗(yàn)功能。
UTF-8編碼中,對(duì)于只需要1個(gè)字節(jié)的字符,就使用一個(gè)字節(jié);對(duì)于中日韓等Unicode中需要兩個(gè)字節(jié)才能表示的字符,則通過一個(gè) UTF16 – UTF8 的算法實(shí)現(xiàn)相互之間的轉(zhuǎn)換(轉(zhuǎn)換后的UTF-8一般需要3個(gè)字節(jié)),而對(duì)于Unicode中需要4個(gè)字節(jié)才能表示的字符,UTF-8根據(jù)需要可以擴(kuò)展到6個(gè)字節(jié)來表示一個(gè)字符。UTF8使用的算法很有意思,大致映射關(guān)系如下:
UTF-32 UTF8
0x00000000 - 0x0000007F 0xxxxxxx
0x00000080 - 0x000007FF 110xxxxx 10xxxxxx
0x00000800 - 0x0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
0x00010000 - 0x001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
0x00200000 - 0x03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
0x04000000 - 0x7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
可以發(fā)現(xiàn)這和IP的分址算法很是相像,詳細(xì)的映射規(guī)則可以參考這里。
由于UTF-8可以方便的轉(zhuǎn)換為UTF16和UTF32(不需要碼表,執(zhí)行一個(gè)轉(zhuǎn)換算法即可,在Unicode.org上提供了C代碼)。而且UTF-8在每個(gè)操作系統(tǒng)平臺(tái)上的實(shí)現(xiàn)都是一樣的,也不存在跨平臺(tái)的問題,所以UTF-8成為跨平臺(tái)的Unicode很好的解決方案。當(dāng)然,對(duì)于中文來說,由于每個(gè)字符需要3個(gè)字節(jié)才能表示,還是有點(diǎn)浪費(fèi)的。
注意,雖然說UTF-8是為了使用更少的空間而使用的,但那只是相對(duì)于Unicode編碼來說,如果已經(jīng)知道是漢字,則使用GB2312/GBK無疑是最節(jié)省的。不過另一方面,值得說明的是,對(duì)于中文網(wǎng)頁,雖然UTF-8編碼對(duì)漢字使用3個(gè)字節(jié),UTF8編碼也會(huì)比UTF-16編碼節(jié)省,因?yàn)榫W(wǎng)頁HTML中包含了更多的英文字符。
UTF-8 是不需要BOM來表明字節(jié)順序,但可以用BOM來表明編碼方式。字符"ZWNBSP"即“0xFEFF”的UTF-8編碼是EF BB BF(根據(jù)上表轉(zhuǎn)換關(guān)系)。所以如果接收者收到以EF BB BF開頭的字節(jié)流,就知道這是通知其收到的是UTF-8編碼了。
Windows系統(tǒng)就是用BOM來標(biāo)記文本文件的編碼方式的。用UltraEdit等編輯器的16進(jìn)制編輯模式查看UTF-8編碼的文件,都是EF BB BF開頭的,說明都是帶BOM的。參照下面的GB2312/GBK的編碼,可以解釋為什么在出現(xiàn)編碼問題時(shí),經(jīng)常看到這三個(gè)漢字“锘匡豢”:
(完)
參考資料:
[1] TechGuru: http://tunps.com/link-and-script-goes-under-body-tag
[2] 網(wǎng)頁編碼就是那點(diǎn)事|潛行者m:http://www.qianxingzhem.com/post-1499.html
本文固定鏈接: http://blog.xieyc.com/common-code-standard-unicode-utf-iso-8859-1-etc/ | 小謝的小站
聯(lián)系客服