人類交流使用 A
、B
、C
、中
等字元,但計算機只認識 0
和 1
。因此,就需要將人類的字元,轉換成計算機認識的二進位制編碼。這個過程就是字元編碼。
最簡單、常用的字元編碼就是 ASCII(American Standard Code for Information Interchange,美國資訊交換標準程式碼),它將美國人最常用的 26 個英文字元的大小寫和常用的標點符號,編碼成 0
到 127
的數位。例如 A
對映成 65
(0x41
),這樣計算機中就可以用 0100 0001
這組二進位制資料,來表示字母 A
了。
ASCII 編碼的字元可以分成兩類:
0
- 31
和 127
(0x00
- 0x1F
和 0x7F
)32
- 126
(0x20
- 0x7E
)具體字元表可以參考:ASCII - 維基百科,自由的百科全書。
ASCII 只編碼了美國常用的 128 個字元。顯然不足以滿足世界上這麼多國家、這麼多語言的字元使用。於是各個國家和地區,就都開始對自己需要的字元設計其他編碼方案。例如,中國有自己的 GB2312,不夠用了之後又擴充套件了 GBK,還是不夠用,又有了 GB18030。歐洲有一系列的 ISO-8859 編碼。這樣各國人民就都可以在計算機上處理自己的語言文字了。
但每種編碼方案,都只考慮了自己用到的字元,沒辦法跨服交流。如果一篇檔案裡,同時使用了多種語言的字元,總不能分別指定哪個字元使用了那種編碼方式。
如果能統一給世界上的所有字元分配編碼,就可以解決跨服交流的問題了,Unicode 就是來幹這個事情的。
Unicode 統一編碼了世界上大部分的字元,例如將 A
編碼成 0x00A1
,將 中
編碼成 0x4E2D
,將 α
編碼成 0x03B1
。這樣,中國人、美國人、歐洲人,就可以使用同一種編碼方式交流了。
一個 Unicode 字元可以使用 U+
和 4 到 6 個十六進位制數位來表示。例如 U+0041
表示字元 A
、U+4E2D
表示字元 中
,U+03B1
表示字元 α
。
Unicode 最初編碼的範圍是 0x0000
到 0xFFFF
,也就是兩個位元組,最多 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。
「字元編碼」是一個模糊、籠統的概念,為了進一步說明字元編碼的過程,需要將其拆解為一些更加明確的概念:
人類使用的字元。例如:
A
;中
等。把一些字元的集合 (Character Set) 中的每個字元 (Character),對映成一個編號或座標。例如:
A
編號為 65
(0x41
);中
編號為 0x4E2D
;中
對映到第 54 區第 0 位。這個對映的編號或座標,叫做 Code Point。
Unicode 就是一個 CCS。
把 Code Point 轉換成特定長度的整型值的序列。這個特定長度的整型值叫做 Code Unit。例如:
0x41
這個 Code Point 會被轉換成 0x41
這個 Code Unit;0x4E2D
這個 Code Point 會被轉換成 0xE4 B8 AD
這三個 Code Unit 的序列。我們常用的 UTF-8、UTF-16 等,就是 CEF。
把 Code Unit 序列轉換成位元組序列(也就是最終編碼後的二進位制資料,供計算機使用)。例如 :
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 只是把字元對映成了 Code Point (字元編碼表,CCS)。將 Code Point 轉換成 Code Unit 序列(字元編碼表,CEF),再最終將 Code Unit 序列轉換成位元組序列(字元編碼方案,CES),有多種不同的實現方式。這些實現方式叫做 Unicode 轉換格式 (Unicode Transformation Format, UTF)。主要包括:
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 將每個 Unicode Code Point 轉換成 1 到 2 個 16 位長的 Code Unit。
對於基本平面的 Code Point(0x0000
到 0xFFFF
),每個 Code Point 轉換成 1 個 Code Unit,Code Unit 的值就是其對應 Code Point 的值。例如 0x0041
這個 Code Unit,就表示了 0x0041
這個 Code Point。
對於輔助平面的 Code Point(0x010000
到 0x10FFFF
),每個 Code Point 轉換成 2 個 Code Unit 的序列。如果還是直接使用 Code Point 數值轉換成 Code Unit,就有可能和基本平面的編碼重疊。例如 U+010041
如果轉換成 0x0001
、0x0041
這兩個 Code Unit,解碼的時候沒辦法知道這是 U+010041
一個字元,還是 U+0001
和 U+0041
兩個字元。
為了讓輔助平面編碼的兩個 Code Unit,都不與基本平面編碼的 Code Unit 重疊,就需要利用基本平面中一個特殊的區段了。基本平面中規定了從 0xD800
到 0xDFFF
之間的區段,是永久保留不對映任何字元的。UTF-16 將輔助平面的 Code Point,編碼成一對在這個範圍內的 Code Unit,叫做代理對。這樣解碼的時候,如果解析到某個 Code Unit 在 0xD800
到 0xDFFF
範圍內,就知道他不是基本平面的 Code Unit,而是要兩個 Code Unit 組合在一起去表示 Code Point。
具體轉換方式是:
0x010000
- 0x10FFFF
),減去 0x010000
,得到 0x00000
到 0xFFFFF
範圍內的一個數值,也就是最多 20 個位元位的數值0x0000
到 0x03FF
),加上 0xD800
,得到範圍在 0xD800
到 0xDBFF
的一個值,作為第一個 Code Unit,稱作高位代理或前導代理0x0000
到 0x03FF
),加上 0xDC00
,得到範圍在 0xDC00
到 0xDFFF
的一個只,作為第二個 Code Unit,稱作低位代理或後尾代理基本平面中的 0xD800
- 0xDBFF
和 0xDC00
- 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 將每個 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 Unit0b1110
,說明這個 Code Point 是三個 Code Unit 組成,後面還會有 2 個 0b10
字首的 Code Unit0b11110
,說明這個 Code Point 是四個 Code Unit 組成,後面還會有 3 個 0b10
字首的 Code UnitUTF-8 的一個 Code Unit,剛好轉換成 1 個位元組,因此不需要考慮位元組序。
參考上表,對於 ASCII 範圍內的字元,使用 ASCII 和 UTF-8 編碼的結果是一樣的。所以 UTF-8 是 ASCII 的超集,使用 ASCII 編碼的位元組流也可以使用 UTF-8 解碼。
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 |
可以看出只有在 0x00
到 0xEF
範圍的字元,UTF-8 編碼比 UTF-16 短;而在 0x0800
- 0xFFFF
範圍內,UTF-8 編碼是比 UTF-16 長的。
而中文主要在 0x4E00
到 0x9FFF
,如果寫一篇檔案,全都是中文,一個英文字母和符號都沒有。那使用 UTF-8 編碼,可能比 UTF-16 編碼還要多佔用一半的空間。
相關文章: