詳解字元編碼與 Unicode

2022-09-19 06:02:27

人類交流使用 ABC 等字元,但計算機只認識 01。因此,就需要將人類的字元,轉換成計算機認識的二進位制編碼。這個過程就是字元編碼。

ASCII

最簡單、常用的字元編碼就是 ASCII(American Standard Code for Information Interchange,美國資訊交換標準程式碼),它將美國人最常用的 26 個英文字元的大小寫和常用的標點符號,編碼成 0127 的數位。例如 A 對映成 65 (0x41),這樣計算機中就可以用 0100 0001 這組二進位制資料,來表示字母 A 了。

ASCII 編碼的字元可以分成兩類:

  • 控制字元:0 - 31127 (0x00 - 0x1F0x7F)
  • 可顯示字元:32 - 126 (0x20 - 0x7E)

具體字元表可以參考:ASCII - 維基百科,自由的百科全書

Unicode

ASCII 只編碼了美國常用的 128 個字元。顯然不足以滿足世界上這麼多國家、這麼多語言的字元使用。於是各個國家和地區,就都開始對自己需要的字元設計其他編碼方案。例如,中國有自己的 GB2312,不夠用了之後又擴充套件了 GBK,還是不夠用,又有了 GB18030。歐洲有一系列的 ISO-8859 編碼。這樣各國人民就都可以在計算機上處理自己的語言文字了。

但每種編碼方案,都只考慮了自己用到的字元,沒辦法跨服交流。如果一篇檔案裡,同時使用了多種語言的字元,總不能分別指定哪個字元使用了那種編碼方式。

如果能統一給世界上的所有字元分配編碼,就可以解決跨服交流的問題了,Unicode 就是來幹這個事情的。

Unicode 統一編碼了世界上大部分的字元,例如將 A 編碼成 0x00A1,將 編碼成 0x4E2D,將 α 編碼成 0x03B1。這樣,中國人、美國人、歐洲人,就可以使用同一種編碼方式交流了。

一個 Unicode 字元可以使用 U+ 和 4 到 6 個十六進位制數位來表示。例如 U+0041 表示字元 AU+4E2D 表示字元 U+03B1 表示字元 α

Unicode 最初編碼的範圍是 0x00000xFFFF,也就是兩個位元組,最多 65536 (2^16) 個字元。但隨著編碼的字元越來越多,兩個位元組的編碼空間已經不夠用,因此又引入了 16 個輔助平面,每個輔助平面同樣最多包含 65536 個字元。原來的編碼範圍稱為基本平面,也叫第 0 平面。

各平面的字元範圍和名稱如下表:

平面 字元範圍 名稱
0 號平面 U+0000 - U+FFFF 基本多文種平面 (Basic Multilingual Plane, BMP)
1 號平面 U+10000 - U+1FFFF 多文種補充平面 (Supplementary Multilingual Plane, SMP)
2 號平面 U+20000 - U+2FFFF 表意文字補充平面 (Supplementary Ideographic Plane, SIP)
3 號平面 U+30000 - U+3FFFF 表意文字第三平面 (Tertiary Ideographic Plane, TIP)
14 號平面 U+E0000 - U+EFFFF 特別用途補充平面
15 號平面 U+F0000 - U+FFFFF 保留作為私人使用區(A 區)(Private Use Area-A, PUA-A)
16 號平面 U+100000 - U+10FFFF 保留作為私人使用區(B 區)(Private Use Area-B, PUA-B)

每個平面內還會進一步劃分成不同的區段。每個平面和區段具體說明參考 Unicode字元平面對映 - 維基百科,自由的百科全書;漢字相關的區段說明參考 中日韓統一表意文字 - 維基百科,自由的百科全書。Unicode 所有字元按平面和區段查詢,可以參考 Roadmaps to Unicode;按區域和語言查詢可以參考 Unicode Character Code Charts

字元編碼的基本概念

「字元編碼」是一個模糊、籠統的概念,為了進一步說明字元編碼的過程,需要將其拆解為一些更加明確的概念:

字元 (Character)

人類使用的字元。例如:

  • A
  • 等。

編碼字元集 (Coded Character Set, CCS)

把一些字元的集合 (Character Set) 中的每個字元 (Character),對映成一個編號或座標。例如:

  • 在 ASCII 中,把 A 編號為 65 (0x41);
  • 在 Unicode 中,把 編號為 0x4E2D
  • 在 GB2312 中,把 對映到第 54 區第 0 位。

這個對映的編號或座標,叫做 Code Point。

Unicode 就是一個 CCS。

字元編碼表 (Character Encoding Form, CEF)

把 Code Point 轉換成特定長度的整型值的序列。這個特定長度的整型值叫做 Code Unit。例如:

  • 在 ASCII 中,0x41 這個 Code Point 會被轉換成 0x41 這個 Code Unit;
  • 在 UTF-8 中,0x4E2D 這個 Code Point 會被轉換成 0xE4 B8 AD 這三個 Code Unit 的序列。

我們常用的 UTF-8、UTF-16 等,就是 CEF。

字元編碼方案 (Character Encoding Scheme, CES)

把 Code Unit 序列轉換成位元組序列(也就是最終編碼後的二進位制資料,供計算機使用)。例如 :

  • 0x0041 這個 Code Unit,使用大端序會轉換成 0x00 41 兩個位元組;
  • 使用小端序會轉換成 0x41 00 兩個位元組。

UTF-16 BE、UTF-32 LE 等,就是 CES。


這些概念間的關係如下:

因此,我們說 ASCII 是「字元編碼」時,「字元編碼」指的是上面從 Character 到位元組陣列的整個過程。因為 ASCII 足夠簡單,中間的 Code Point 到 Code Unit,再到位元組陣列,都是一樣的,沒必要拆開說。

而我們說 Unicode 是「字元編碼」時,「字元編碼」其實指的僅是上面的 CCS 部分。

同理,ASCII、Unicode、UTF-8、UTF-16、UTF-16 LE,都可以籠統的叫做「字元編碼」,但每個「字元編碼」表示的含義都是不同的。可能是 CCS、CEF、CES,也可能是整個過程。

Unicode 轉換格式

Unicode 只是把字元對映成了 Code Point (字元編碼表,CCS)。將 Code Point 轉換成 Code Unit 序列(字元編碼表,CEF),再最終將 Code Unit 序列轉換成位元組序列(字元編碼方案,CES),有多種不同的實現方式。這些實現方式叫做 Unicode 轉換格式 (Unicode Transformation Format, UTF)。主要包括:

  • UTF-32
  • UTF-16
  • UTF-8

UTF-32

UTF-32 將每個 Unicode Code Point 轉換成 1 個 32 位長的 Code Unit。

UTF-32 是固定長度的編碼方案,每個 Code Unit 的值就是其 Code Point 的值。例如 0x00 00 00 41 這個 Code Unit,就表示了 0x0041 這個 Code Point。

UTF-32 的一個 Code Unit,需要轉換成 4 個位元組的序列。因此,有大端序 (UTF-32 BE) 和小端序 (UTF-32 LE) 兩種轉換方式。

例如 0x00 00 00 41 這個 Code Unit,使用 UTF-32 BE 最終會編碼為 0x00 00 00 41;使用 UTF-32 LE 最終會編碼為 0x41 00 00 00

UTF-16

UTF-16 將每個 Unicode Code Point 轉換成 1 到 2 個 16 位長的 Code Unit。

對於基本平面的 Code Point(0x00000xFFFF),每個 Code Point 轉換成 1 個 Code Unit,Code Unit 的值就是其對應 Code Point 的值。例如 0x0041 這個 Code Unit,就表示了 0x0041 這個 Code Point。

對於輔助平面的 Code Point(0x0100000x10FFFF),每個 Code Point 轉換成 2 個 Code Unit 的序列。如果還是直接使用 Code Point 數值轉換成 Code Unit,就有可能和基本平面的編碼重疊。例如 U+010041 如果轉換成 0x00010x0041 這兩個 Code Unit,解碼的時候沒辦法知道這是 U+010041 一個字元,還是 U+0001U+0041 兩個字元。

為了讓輔助平面編碼的兩個 Code Unit,都不與基本平面編碼的 Code Unit 重疊,就需要利用基本平面中一個特殊的區段了。基本平面中規定了從 0xD8000xDFFF 之間的區段,是永久保留不對映任何字元的。UTF-16 將輔助平面的 Code Point,編碼成一對在這個範圍內的 Code Unit,叫做代理對。這樣解碼的時候,如果解析到某個 Code Unit 在 0xD8000xDFFF 範圍內,就知道他不是基本平面的 Code Unit,而是要兩個 Code Unit 組合在一起去表示 Code Point。

具體轉換方式是:

  1. 將輔助平面的 Code Point 的值 (0x010000 - 0x10FFFF),減去 0x010000,得到 0x000000xFFFFF 範圍內的一個數值,也就是最多 20 個位元位的數值
  2. 將前 10 位的值(範圍在 0x00000x03FF),加上 0xD800,得到範圍在 0xD8000xDBFF 的一個值,作為第一個 Code Unit,稱作高位代理或前導代理
  3. 將後 10 位的值(範圍在 0x00000x03FF),加上 0xDC00,得到範圍在 0xDC000xDFFF 的一個只,作為第二個 Code Unit,稱作低位代理或後尾代理

基本平面中的 0xD800 - 0xDBFF0xDC00 - 0xDFFF 這兩個區段,也分別叫做 UTF-16 高半區 (High-half zone of UTF-16) 和 UTF-16 低半區 (Low-half zone of UTF-16)。

UTF-16 的一個 Code Unit,需要轉換成 2 個位元組的序列。因此,有大端序 (UTF-16 BE) 和小端序 (UTF-16 LE) 兩種轉換方式。

例如 0x0041 這個 Code Unit,使用 UTF-16 BE 最終會編碼為 0x0041;使用 UTF-16 LE 最終會編碼為 0x4100

UTF-8

UTF-8 將每個 Unicode Code Point 轉換成 1 到 4 個 8 位長的 Code Unit。

UTF-8 是不定長的編碼方案,使用字首來標識 Code Unit 序列的長度。解碼時,根據字首,就知道該將哪幾個 Code Unit 組合在一起解析成一個 Code Point 了。

具體編碼方式是:

Code Point 範圍 Code Unit 個數 每個 Code Unit 字首 範例 Code Point 範例 Code Unit 序列
7 位以內 (0 - 0xEF) 1 0b0 0b0zzz zzzz 0b0zzz zzzz
8 到 11 位 (0x80 - 0x07FF) 2 第一個 0b110,剩下的 0b10 0b0yyy yyzz zzzz 0b110y yyyy 10zz zzzz
12 到 16 位 (0x0800 - 0xFFFF) 3 第一個 0b1110,剩下的 0b10 0bxxxx yyyy yyzz zzzz 0b1110 xxxx 10yy yyyy 10zz zzzz
17 到 21 位 (0x10000 - 10FFFF) 4 第一個 0b11110,剩下的 0b10 0b000w wwxx xxxx yyyy yyzz zzzz 0b1111 0www 10xx xxxx 10yy yyyy 10zz zzzz

解碼時,拿到每個 Code Unit 的字首,就知道這是對應第幾個 Code Unit:

  • 字首是 0b0,說明這個 Code Point 是一個 Code Unit 組成
  • 字首是 0b110,說明這個 Code Point 是兩個 Code Unit 組成,後面還會有 1 個 0b10 字首的 Code Unit
  • 字首是 0b1110,說明這個 Code Point 是三個 Code Unit 組成,後面還會有 2 個 0b10 字首的 Code Unit
  • 字首是 0b11110,說明這個 Code Point 是四個 Code Unit 組成,後面還會有 3 個 0b10 字首的 Code Unit

UTF-8 的一個 Code Unit,剛好轉換成 1 個位元組,因此不需要考慮位元組序。

參考上表,對於 ASCII 範圍內的字元,使用 ASCII 和 UTF-8 編碼的結果是一樣的。所以 UTF-8 是 ASCII 的超集,使用 ASCII 編碼的位元組流也可以使用 UTF-8 解碼。

UTF-8 與 UTF-16 對比

Code Point 範圍 UTF-8 編碼長度 UTF-16 編碼長度
7 位以內 (0x00 - 0xEF) 1 2
8 到 11 位 (0x0080 - 0x07FF) 2 2
12 到 16 位 (0x0800 - 0xFFFF) 3 2
17 到 21 位 (0x10000 - 10FFFF) 4 4

可以看出只有在 0x000xEF 範圍的字元,UTF-8 編碼比 UTF-16 短;而在 0x0800 - 0xFFFF 範圍內,UTF-8 編碼是比 UTF-16 長的。

而中文主要在 0x4E000x9FFF,如果寫一篇檔案,全都是中文,一個英文字母和符號都沒有。那使用 UTF-8 編碼,可能比 UTF-16 編碼還要多佔用一半的空間。


相關文章: