0. 從ASCII碼說起
學(xué)過電腦的人都聽說過
ASCII碼,這是一種根據(jù)英文字符表設(shè)計的字符編碼。嚴格意義上來講,標準ASCII碼只有7位(最高位為0),共128個字符,用十進制表示是:0-127。其中0-31和127為
控制字符,而32-126為
顯示字符。國際標準化組織還發(fā)布了一些8位ASCII碼擴展字符集,其中最流行的就是
ISO 8859-1 (ISO Latin1)。它除了兼容標準的7位ASCII字符集以外,還利用高字節(jié)位擴充了一些西歐國家常用的字符和符號。盡管如此,由于ASCII碼屬于單字節(jié)編碼(
SBCS, or Single Byte Character Set),所以它最多只能表示256個字符。
顯然,ASCII碼“不懂中文"。那誰懂呢?
1. DBCS路過
記得99年我剛接觸電腦的時候,還能看到UCDOS的身影。那會兒經(jīng)常聽老師說一句話:“一個漢字包含兩個字節(jié)”——這其實就是
DBCS (Double Byte Character Set,雙字節(jié)字符集)使用的方法。DBCS是最常見的MBCS(Multi-Byte Character Set,多字節(jié)字符集),它保留了ASCII碼的128個字符(0-127),并且對一般的非拉丁字符采用雙字節(jié)編碼。第一個字節(jié)(前導(dǎo)字節(jié),Lead Byte)使用一個大于127的數(shù)字(即高字節(jié)位為1),和第二字節(jié)(尾隨字節(jié),Trailing Byte)一起組成一個字符。所以,在DBCS里面,ASCII字符仍占1個字節(jié),而象中文、日文等字符則占2個字節(jié)。
我們常使用的DBCS編碼有:
字符編碼
第一字節(jié)范圍
第二字節(jié)范圍
第一字節(jié)最高位
第二字節(jié)最高位
字符數(shù)
說明
GB2312
(1980)
A1..F7
A1..FE
11
7445
簡體字符集(基于區(qū)位碼設(shè)計,高低字節(jié)各加了$A0)
Big5
(1984, CP950)
81..FE
40..7E A1..FE
10或1
13461
繁體字符集(香港、臺灣、澳門等)
GBK 1.0
(1993, CP936)
81..FE
40..FE 80..FE
1
0或1
21886
兼容GB2312,支持Unicode 1.1中定義的CJK(簡/繁體中文、日語和韓語)
GB18030
(2000, CP54936)27484兼容GBK1.0,支持Unicode3.1
有單字節(jié)、雙字節(jié)和四字節(jié)三種方式
表1.1 常見的DBCS編碼
備注:
GB2312分為二級:一級為常用字,3755個,按照拼音排序;二級為次常用字,3008個,按照部首排序。 GBK 1.0是Microsoft對GB2312的擴展。
說到這里,就要引入一個很重要的概念——代碼頁(Code Page, CP)。我們常說的代碼頁實際上是Windows為不同的字符編碼方案所分配的一個數(shù)字編號。下面列舉了一些常見的代碼頁:
代碼頁描述
CP932日語 (Shift-JIS)
CP936簡體中文 (GBK)
CP950繁體中文 (Big5)
CP1252Windows (ANSI) Codepage, 基于ISO-8859-1標準
CP1200UCS-2LE (Unicode little-endian)
CP1201UCS-2BE (Unicode big-endian)
CP12000UTF-32 LE
CP12000UTF-32 BE
CP54936GB18030
CP65001UTF-8 Unicode
表1.2 常見的Windows代碼頁
DBCS編碼總是和系統(tǒng)的代碼頁聯(lián)系在一起的。在不同的代碼頁里面,同一串?dāng)?shù)據(jù)可能會被映射成 不同的字符。比如“BA BA D7 D6 41 42 43”,在簡體中文環(huán)境下(GBK,CP936),它會被映射成成“漢字ABC”;若當(dāng)前代碼頁為繁體中文 (Big5, CP950),它就變成了“犖趼ABC”;如果選擇ISO8859-1代碼頁的話,那我們會看到“ooó?ABC”——這就是我們常說的亂碼,即沒有選擇正確的編碼。另外當(dāng)某個字符編碼在當(dāng)前代碼頁中不存在時,系統(tǒng)會用一個特殊的符號來顯示它(方框或問號)。比如將“?2007 Google”保存為ANSI文本文件,再打開時就變成了“?2007 Google”。
另外,不知道您發(fā)現(xiàn)了沒有,對于DBCS編碼的文本來說,如果其中有一個字節(jié)丟失或損壞,就可能造成整個文件出現(xiàn)亂碼的情況。比如“.Net與字符編碼(理論篇)”在內(nèi)存中表示為:
“2E 4E 65 74 D3 EB D7 D6 B7 FB B1 E0 C2 EB A3 A8 C0 ED C2 DB C6 AA A3 A9”
其中D3 EB是“與”的內(nèi)碼(GBK),此時剛好前導(dǎo)字節(jié)和尾隨字節(jié)都大于7F (127)。如果這時候我們把D3改成00,保存后重新打開。你猜會怎么樣?它已經(jīng)變得面目全非了:“.Net 胱址 嗦耄ɡ礪燮 ”。
所以說,DBCS編碼存儲效率比較高,但可惜的是它包含的字符數(shù)量有限,最重要的是我們不能同時使用不同語言中的字符。而且從上面的例子可以看出,DBCS編碼并不是很適合網(wǎng)絡(luò)傳輸。
那我們該怎么辦呢?
2. Unicode登場
字符是語言中最小的邏輯單位,而
Unicode所做的就是為每個字符分配一個唯一的數(shù)字(Code Point,字位)并定義一系列規(guī)則,并且與程序、平臺以及語言無關(guān)。使用Unicode編碼,我們就可以用一種統(tǒng)一的方式來表示和處理不同語言中的字符,而這在以前是不可能做到的。(比如,“漢字ABC????????????“)。Unicode編碼僅僅兼容了ISO-8859-1標準,比如U+0041表示'A',
U+6C49來表示的(其GBK內(nèi)碼是BABA)。
ISO10646標準曾經(jīng)定義了兩種Unicode字符集方案UCS (
Universal Character Set):分別是UCS-2和UCS-4(2 字節(jié)編碼和4字節(jié)編碼)。采用UCS-4編碼的字符有31位(最高位為0),理論上有0x80000000 (U-00000000-U-7FFFFFFF)個字位。整個編碼空間(Code Space)分成128個組,每組有256個平面,每一平面包含256行,每行又有256個字位。其中00組的00平面(即第零平面)被稱為基本多文種平面 (BMP,
Basic Multilingual Plane)。BMP共含有256*256=65536個字位,這么大的空間已經(jīng)包含了當(dāng)今世界上絕大多數(shù)常用字符(Wikipedia上有一張關(guān)于BMP如何分配的
Roadmap,有興趣的話可以看看)。UCS-2就是對BMP中的字符進行編碼,無論英文字符還是非拉丁字符,均用2個字節(jié)表示,顯然UCS-2只是UCS-4的一個子集。
據(jù)資料顯示,現(xiàn)存的漢字已經(jīng)超過了9萬。顯然對于某些專業(yè)領(lǐng)域來說,BMP收錄的2萬多漢字是遠遠不夠用的。2000年國家為了爭取主動權(quán),開始對我國境內(nèi)銷售的非嵌入式軟件產(chǎn)品強制實行GB18030標準。該標準除了兼容GB2312和GBK以外,還收錄了CJK擴展字符集A中的6千多漢字。它同時定義了4字節(jié)編碼,為我國少數(shù)民族及以后的擴展保留了大量的可用編碼空間。
在這樣的環(huán)境下,Unicode組織推出了UTF-16和UTF-32編碼方案,同時取代了原有的UCS-2和UCS-4。UTF(Unicode Transformation Format)編碼格式保留了UCS-4中的前17個平面(U+000000 - U+10FFFF)作為有效編碼空間。其中UTF-16是UCS-2的擴展:對于BMP內(nèi)的字符,它和UCS-2的編碼相同(2字節(jié));對于BMP以外的字符則采用一對16位字組合(surrogate pairs)的方式進行編碼(4字節(jié))。組合編碼的高字位(High Surrogate)在U+D800–U+DBFF,低字位(Low Surrogate)在U+DC00-U+DFFF。UTF-32則是在有效編碼空間范圍內(nèi),對所有字符全部使用4字節(jié)編碼。
除了UTF-16和UTF-32,還有一種常用的Unicode編碼UTF-8。接下來我們分析這幾種編碼有什么區(qū)別。使用系統(tǒng)自帶的記事本和Debug工具,我們可以比較,在不同編碼下將字符串"漢字ABC"保存到Text文件后在內(nèi)存中的表示:
編碼形式編碼結(jié)果(含BOM)
UTF-8EF BB BF E6 B1 89 E8 AF AD 41 42 43
UTF-16 LEFF FE 49 6C ED 8B 41 00 42 00 43 00
UTF-16 BEFE FF 6C 49 8B ED 00 41 00 42 00 43
UTF-32 LEFF FE 00 00 49 6C 00 00 ED 8B 00 00 41 00 00 00 42 00 00 00 43 00 00 00
UTF-32 BE00 00 FE FF 00 00 6C 49 00 00 8B ED 00 00 00 41 00 00 00 42 00 00 00 43
表2.1 Unicode編碼形式
我們發(fā)現(xiàn),在Windows的平面文件中,每種Unicode編碼都用一串字節(jié)來標識自己。我們把這串連續(xù)的字節(jié)稱為
BOM (Byte-order Mark,字節(jié)順序標識)。它使用一個特殊字符U+FEFF (ZERO WIDTH NO-BREAK SPACE)來表示編碼形式和字節(jié)順序。
為什么會有字節(jié)順序呢?說道這里,就要補一補基本知識了?,F(xiàn)代的計算機系統(tǒng)一般采用字節(jié)(Octet, 8 bit Byte)作為邏輯尋址單位。當(dāng)物理單位的長度大于1個字節(jié)時,就要區(qū)分字節(jié)順序(Byte Order, or
Endianness)。常見的字節(jié)順序有兩種:Big Endian(High-byte first)和Little Endian(Low-byte first),這就是表2.1中的BE和LE。Intel X86平臺采用Little Endian,而PowerPC處理器則采用了Big Endian。舉例來說,整型數(shù)字$1234ABCD存儲的時候就會有兩種方式:
字節(jié)順序內(nèi)存數(shù)據(jù)備注
Big Endian (BE)
0xAB 0xCD 0x12 0x34此時的0xAB被稱為
most significant byte (MSB)
Little Endian (LE)
0xCD 0xAB 0x34 0x12此時的0xCD被稱為
least significant byte (LSB)
表2.2 字節(jié)順序
需要注意的是,DBCS編碼仍以字節(jié)作為基本編碼單元,因而不會有字節(jié)順序問題,而Unicode使用16位字(WORD)來編碼,所以才會有LE和BE之分。另外從表2.1中我們可以看出UTF-8并沒有字節(jié)順序問題,使用BOM只是為了標識而已。
接下來我們一起看看UTF-8的編碼方法:
編碼范圍
UTF-8
U-00000000 – U-0000007F0xxxxxxx
U-00000080 – U-000007FF110xxxxx 10xxxxxx
U-00000800 – U-0000FFFF1110xxxx 10xxxxxx 10xxxxxx
U-00010000 – U-001FFFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
表2.3 UTF-8 編碼方法
我們先看看“漢”字的UTF-8編碼?!皾h”字U+6C49用二進制表示是:0110 1100 0100 1001,對應(yīng)U-00000800 – U-0000FFFF區(qū)間,將這些二進制位放入對應(yīng)的位置后就變成了:11100110 10110001 10001001,再將其轉(zhuǎn)換成十六進制是E6 B1 89。因此我們不難算出字符串“漢字ABC”用UTF-8編碼后變?yōu)椤癊6 B1 89 E8 AF AD 41 42 43”,這也和表2.1中的數(shù)據(jù)一致。
分析完常見的幾種Unicode編碼格式,我們來總結(jié)一下這幾種編碼格式所需要的字節(jié)個數(shù)以及各自的優(yōu)缺點。
編碼范圍
UTF-8
UTF-16
UTF-32
GB18030 *
U+000000 – U+00007F
1
2
4
1
U+000080 – U+00009F
U+0000A0 – U+0003FF
U+000400 – U+0007FF
2
2
4
2
U+000800 – U+003FFF
U+004000 – U+00FFFF
3
2
4
2
U+010000 – U+03FFFF
U+040000 – U+10FFFF
4
4
4
4
表2.4 常見Unicode編碼所需的字節(jié)數(shù)
備注:GB18030并不是真正意義上的Unicode編碼,僅作參考
UTF-8、UTF-16和UTF-32都可以表示有效編碼空間(U+000000-U+10FFFF)內(nèi)的所有Unicode字符。
使用UTF-8編碼時ASCII字符只占1個字節(jié),存儲效率比較高,適用于拉丁字符較多的場合以節(jié)省空間。
對于大多數(shù)非拉丁字符(如中文和日文)來說,UTF-16所需存儲空間最小,每個字符只占2個字節(jié)。
Windows NT內(nèi)核是Unicode(UTF-16),采用UTF-16編碼在調(diào)用系統(tǒng)API時無需轉(zhuǎn)換,處理速度也比較快。
采用UTF-16和UTF-32會有LE和BE之分,而UTF-8則沒有字節(jié)順序問題,所以UTF-8適合傳輸和通信。
UTF-32采用4字節(jié)編碼,一方面處理速度比較快,但另一方面也浪費了大量空間,影響傳輸速度,因而很少使用