字元編碼(理論篇)【轉】

0. 從ASCII碼說起學過電腦的人都聽說過ASCII碼,這是一種根據英文字元表設計的字元編碼。嚴格意義上來講,標準ASCII碼只有7位(最高位為0),共128個字元,用十進位表示是:0-127。其中0-31和127為控制字元,而32-126為顯示字元。國際標準化組織還發布了一些8位ASCII碼擴展字符集,其中最流行的就是ISO 8859-1 (ISO Latin1)。它除了兼容標準的7位ASCII字符集以外,還利用高位元組位擴充了一些西歐國家常用的字元和符號。儘管如此,由於ASCII碼屬於單位元組編碼(SBCS, or Single Byte Character Set),所以它最多只能表示256個字元。顯然,ASCII碼「不懂中文"。那誰懂呢?1. DBCS路過記得99年我剛接觸電腦的時候,還能看到UCDOS的身影。那會兒經常聽老師說一句話:「一個漢字包含兩個位元組」——這其實就是DBCS (Double Byte Character Set,雙位元組字符集)使用的方法。DBCS是最常見的MBCS(Multi-Byte Character Set,多位元組字符集),它保留了ASCII碼的128個字元(0-127),並且對一般的非拉丁字元採用雙位元組編碼。第一個位元組(前導位元組,Lead Byte)使用一個大於127的數字(即高位元組位為1),和第二位元組(尾隨位元組,Trailing Byte)一起組成一個字元。所以,在DBCS裡面,ASCII字元仍佔1個位元組,而象中文、日文等字元則佔2個位元組。我們常使用的DBCS編碼有:字元編碼第一位元組範圍第二位元組範圍第一位元組最高位第二位元組最高位字元數說明GB2312(1980)A1..F7A1..FE117445簡體字符集(基於區位碼設計,高低位元組各加了$A0)Big5(1984, CP950)81..FE40..7E A1..FE10或113461繁體字符集(香港、台灣、澳門等)GBK 1.0(1993, CP936)81..FE40..FE 80..FE10或121886兼容GB2312,支持Unicode 1.1中定義的CJK(簡/繁體中文、日語和韓語)GB18030(2000, CP54936)27484兼容GBK1.0,支持Unicode3.1有單位元組、雙位元組和四位元組三種方式表1.1 常見的DBCS編碼備註:GB2312分為二級:一級為常用字,3755個,按照拼音排序;二級為次常用字,3008個,按照部首排序。 GBK 1.0是Microsoft對GB2312的擴展。說到這裡,就要引入一個很重要的概念——代碼頁(Code Page, CP)。我們常說的代碼頁實際上是Windows為不同的字元編碼方案所分配的一個數字編號。下面列舉了一些常見的代碼頁:代碼頁描述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 LECP12000UTF-32 BECP54936GB18030CP65001UTF-8 Unicode表1.2 常見的Windows代碼頁DBCS編碼總是和系統的代碼頁聯繫在一起的。在不同的代碼頁裡面,同一串數據可能會被映射成 不同的字元。比如「BA BA D7 D6 41 42 43」,在簡體中文環境下(GBK,CP936),它會被映射成成「漢字ABC」;若當前代碼頁為繁體中文 (Big5, CP950),它就變成了「犖趼ABC」;如果選擇ISO8859-1代碼頁的話,那我們會看到「ooó?ABC」——這就是我們常說的亂碼,即沒有選擇正確的編碼。另外當某個字元編碼在當前代碼頁中不存在時,系統會用一個特殊的符號來顯示它(方框或問號)。比如將「?2007 Google」保存為ANSI文本文件,再打開時就變成了「?2007 Google」。另外,不知道您發現了沒有,對於DBCS編碼的文本來說,如果其中有一個位元組丟失或損壞,就可能造成整個文件出現亂碼的情況。比如「.Net與字元編碼(理論篇)」在內存中表示為:「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是「與」的內碼(GBK),此時剛好前導位元組和尾隨位元組都大於7F (127)。如果這時候我們把D3改成00,保存後重新打開。你猜會怎麼樣?它已經變得面目全非了:「.Net 胱址 嗦耄ɡ礪燮 」。所以說,DBCS編碼存儲效率比較高,但可惜的是它包含的字元數量有限,最重要的是我們不能同時使用不同語言中的字元。而且從上面的例子可以看出,DBCS編碼並不是很適合網路傳輸。那我們該怎麼辦呢?2. Unicode登場字元是語言中最小的邏輯單位,而Unicode所做的就是為每個字元分配一個唯一的數字(Code Point,字位)並定義一系列規則,並且與程序、平台以及語言無關。使用Unicode編碼,我們就可以用一種統一的方式來表示和處理不同語言中的字元,而這在以前是不可能做到的。(比如,「漢字ABC????????????「)。Unicode編碼僅僅兼容了ISO-8859-1標準,比如U+0041表示"A",U+00A9表示"?"。像簡體的"漢"字則是用U+6C49來表示的(其GBK內碼是BABA)。ISO10646標準曾經定義了兩種Unicode字符集方案UCS (Universal Character Set):分別是UCS-2和UCS-4(2 位元組編碼和4位元組編碼)。採用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個字位,這麼大的空間已經包含了當今世界上絕大多數常用字元(Wikipedia上有一張關於BMP如何分配的Roadmap,有興趣的話可以看看)。UCS-2就是對BMP中的字元進行編碼,無論英文字元還是非拉丁字元,均用2個位元組表示,顯然UCS-2隻是UCS-4的一個子集。據資料顯示,現存的漢字已經超過了9萬。顯然對於某些專業領域來說,BMP收錄的2萬多漢字是遠遠不夠用的。2000年國家為了爭取主動權,開始對我國境內銷售的非嵌入式軟體產品強制實行GB18030標準。該標準除了兼容GB2312和GBK以外,還收錄了CJK擴展字符集A中的6千多漢字。它同時定義了4位元組編碼,為我國少數民族及以後的擴展保留了大量的可用編碼空間。在這樣的環境下,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內的字元,它和UCS-2的編碼相同(2位元組);對於BMP以外的字元則採用一對16位字組合(surrogate pairs)的方式進行編碼(4位元組)。組合編碼的高字位(High Surrogate)在U+D800–U+DBFF,低字位(Low Surrogate)在U+DC00-U+DFFF。UTF-32則是在有效編碼空間範圍內,對所有字元全部使用4位元組編碼。除了UTF-16和UTF-32,還有一種常用的Unicode編碼UTF-8。接下來我們分析這幾種編碼有什麼區別。使用系統自帶的記事本和Debug工具,我們可以比較,在不同編碼下將字元串"漢字ABC"保存到Text文件後在內存中的表示:編碼形式編碼結果(含BOM)UTF-8EF BB BF E6 B1 89 E8 AF AD 41 42 43UTF-16 LEFF FE 49 6C ED 8B 41 00 42 00 43 00UTF-16 BEFE FF 6C 49 8B ED 00 41 00 42 00 43UTF-32 LEFF FE 00 00 49 6C 00 00 ED 8B 00 00 41 00 00 00 42 00 00 00 43 00 00 00UTF-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編碼形式我們發現,在Windows的平面文件中,每種Unicode編碼都用一串位元組來標識自己。我們把這串連續的位元組稱為BOM (Byte-order Mark,位元組順序標識)。它使用一個特殊字元U+FEFF (ZERO WIDTH NO-BREAK SPACE)來表示編碼形式和位元組順序。為什麼會有位元組順序呢?說道這裡,就要補一補基本知識了。現代的計算機系統一般採用位元組(Octet, 8 bit Byte)作為邏輯定址單位。當物理單位的長度大於1個位元組時,就要區分位元組順序(Byte Order, orEndianness)。常見的位元組順序有兩種:Big Endian(High-byte first)和Little Endian(Low-byte first),這就是表2.1中的BE和LE。Intel X86平台採用Little Endian,而PowerPC處理器則採用了Big Endian。舉例來說,整型數字$1234ABCD存儲的時候就會有兩種方式:位元組順序內存數據備註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 位元組順序需要注意的是,DBCS編碼仍以位元組作為基本編碼單元,因而不會有位元組順序問題,而Unicode使用16位字(WORD)來編碼,所以才會有LE和BE之分。另外從表2.1中我們可以看出UTF-8並沒有位元組順序問題,使用BOM只是為了標識而已。接下來我們一起看看UTF-8的編碼方法:編碼範圍UTF-8U-00000000 – U-0000007F0xxxxxxxU-00000080 – U-000007FF110xxxxx 10xxxxxxU-00000800 – U-0000FFFF1110xxxx 10xxxxxx 10xxxxxxU-00010000 – U-001FFFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx表2.3 UTF-8 編碼方法我們先看看「漢」字的UTF-8編碼。「漢」字U+6C49用二進位表示是:0110 1100 0100 1001,對應U-00000800 – U-0000FFFF區間,將這些二進位位放入對應的位置後就變成了:11100110 10110001 10001001,再將其轉換成十六進位是E6 B1 89。因此我們不難算出字元串「漢字ABC」用UTF-8編碼後變為「E6 B1 89 E8 AF AD 41 42 43」,這也和表2.1中的數據一致。分析完常見的幾種Unicode編碼格式,我們來總結一下這幾種編碼格式所需要的位元組個數以及各自的優缺點。編碼範圍UTF-8UTF-16UTF-32GB18030 *U+000000 – U+00007F1241U+000080 – U+00009FU+0000A0 – U+0003FFU+000400 – U+0007FF2242U+000800 – U+003FFFU+004000 – U+00FFFF3242U+010000 – U+03FFFFU+040000 – U+10FFFF4444表2.4 常見Unicode編碼所需的位元組數備註:GB18030並不是真正意義上的Unicode編碼,僅作參考UTF-8、UTF-16和UTF-32都可以表示有效編碼空間(U+000000-U+10FFFF)內的所有Unicode字元。使用UTF-8編碼時ASCII字元只佔1個位元組,存儲效率比較高,適用於拉丁字元較多的場合以節省空間。對於大多數非拉丁字元(如中文和日文)來說,UTF-16所需存儲空間最小,每個字元只佔2個位元組。Windows NT內核是Unicode(UTF-16),採用UTF-16編碼在調用系統API時無需轉換,處理速度也比較快。採用UTF-16和UTF-32會有LE和BE之分,而UTF-8則沒有位元組順序問題,所以UTF-8適合傳輸和通信。UTF-32採用4位元組編碼,一方面處理速度比較快,但另一方面也浪費了大量空間,影響傳輸速度,因而很少使用
推薦閱讀:

字元編碼(四)|python2中的編碼問題
字元編碼學習筆記
屁牌兼容1位元組ASCII、2位元組陸港台日常用、單碼最多3位元組、全覆蓋Unicode編碼

TAG:理論 | 字元編碼 | 編碼 | 字元 |