前言
-
先前讀到一篇文章介紹
UTF-8
,突然發現時常轉換來轉換去,卻不知道他的編碼規則究竟是什麼。了解一下順便紀錄一篇。 -
本篇節錄、翻譯自 UTF-8: Bits, Bytes, and Benefits;並加上額外範例。
說明
Code point → Encoding → Hex
UTF-8
是一個 Unicode 的編碼方式,他把整數 (0
~ 10FFFF
,16 進位表示) 轉成 byte stream (0101…);且它實際上比大家想的單純。
參考下表:
Unicode code points UTF-8 encoding (binary) 00~7F ( 7 bits) → 0zzzzzzz 0080~07FF (11 bits) → 110yyyyy 10zzzzzz 0800~FFFF (16 bits) → 1110xxxx 10yyyyyy 10zzzzzz 010000~10FFFF (21 bits) → 11110www 10xxxxxx 10yyyyyy 10zzzzzz
這張表看起來似乎有點複雜,但其實概念很簡單:
第一組
00
~ 7F
,共 128
個數字,剛好覆蓋 ASCII 編碼;這其實是特意兼容 ASCII 所用的範圍。
其中 7F
兩位數字,16 進位中表示:127
,加上 0
共 128 個數字。
將這個數字轉為二進位後,會得到 7
bits,填入表右邊 zzzzzzz
,代表的位置即為 encoding 的結果。
例如
- unicode 編碼
6A
(U+006A
),轉成 7 bit 二進位是:1101010
,所以 encoding 的結果是01101010
;跟 ASCII 完全一樣。
|
|
第二組
0080
~ 07FF
,共 1920
個數字,用來表示各國拼音字母。
維基百科:… 帶有附加符號的拉丁文、希臘文、西里爾字母、亞美尼亞語、希伯來文、阿拉伯文、敘利亞文及它拿字母則需要兩個位元組編碼 (Unicode 範圍由
U+0080
至U+07FF
)
0080
~ 07FF
,數字數量以 16 進位計算:07FF - 007F = 0780
,也就是 2048 - 128 = 1920
個數字。
1920
用二進位表示
111 1000 0000 (共 11 bits)
代表最少需要 11
bits 才能表示。
接著把這 11
bits 前 5 碼填入表右邊的 yyyyy
、後 6 碼填入右邊的 zzzzzz
即代表 encoding 的結果。
例如
- unicode 編碼
026A
(U+026A
),轉成11
bit 是01001101010
,則 encoding 的結果是11001001 10101010
|
|
第三組
0800
~FFFF
仿照一樣的算法會得到 63488
(FFFF - 0800 + 1 = F800
),
不過在編碼上,實際會用到的只有 000800
~ 00D7FF
、00E000
~ 00FFFF
兩段,因此實際會用到的數字是
(D7FF - 0800 + 1) = D000
(FFFF - E000 + 1) = 2000
-------------------------
D000
+ 2000
=======
= F000
16 進位的 F000
就是 61440
,也就是實際會用到的數字是 61440
。
61440 用二進位表示
1111 0000 0000 0000 (16 bits)
代表最少需要 16
bits
接著,同樣把前 4 碼填入表右邊的 xxxx
、中間 6 碼填入 yyyyyy
、最後 6 碼填入 zzzzzz
即代表 encoding 的結果。
- 例如
E12A
這個數字 (U+E12A
),換成二進位是16
bit :1110000100101010
,因此 encoding 的結果是1110 0001 0010 1010
。
|
|
第四組
我們的數字越來越長了 😂
第四組的範圍更寬,從 010000
~ 10FFFF
|
|
16 進位的 100000
是 1048576
,也就是這個區間可以記錄 1048576
(一百多萬) 個字元。
1048576 用二進位表示
100000000000000000000 (21 bits)
代表最少需要 21
bits 才能表示。
參照最上面列的表,第四組是 11110www 10xxxxxx 10yyyyyy 10zzzzzz
,填入就是 enconding 的結果
- 例如
09AB37
這個數字 (U+09AB37
),換成二進位是21
bit :10011010101100110111
,因此 encoding 的結果是11110010 10011010 10101100 10110111
。
|
|
性質
-
所有的 ASCII 檔案就是 UTF-8 檔案,完全兼容。參考上面第一組編碼的說明。
-
在 UTF-8 編碼中看到 ASCII bytes 可以確保是完全一樣的字。例如在 UTF-8 檔案中看到
0x7A
(01111010
),可以確認他一定代表字母z
、ASCII bytes 也不會被其他 UTF-8 編碼覆蓋。 -
而
z
二進位01111010
不會在 UTF-8 中被編碼成11000001 10111010
(第二組的表示法),也就是不會編成00001111010
。原則上是要能用最短的編碼表示;如果編碼失敗,實務上會用 Unicode replacement character (FFFD
) 表示,長這樣:� (似曾相識?) -
UTF-8 is self-synchronizing:
-
仔細觀察上表的右手邊,除了開頭第一組之外,其他組的內部、後面跟隨的 bytes 的都是
10______
開頭。 -
例如第三組
1110xxxx 10yyyyyy 10zzzzzz
-
這些 bytes 稱作 continuation byte,代表是同一組 encoding 的字元,也就能快速找到下一個字的開頭 — 下一組 bytes 如果不是
10
______ ,即是另一個字的開頭。
-
-
字串搜尋 (substring search) 只是 bytes string search:從以上性質可知,只要對用一組組 bytes (8-bits) 比對即可完成字串搜尋的任務。
-
大部分的程式,如果能正常處理 8-bits 檔案,就成正常處理 UTF-8 編碼檔案:
-
之所以說「大部分」是因為,如果某些程式會把這些 bytes 拆開視為個別字元的話就會出錯;不過現在已經很少程式會有這種狀況了。更常見的情況是以換行
\n
或是空白當作字元分隔的符號,這與 ASCII 的編碼是相同的。也因此 Unix Tool 像是cat
、cp
、diff
、echo
、head
、tail
等指令都能處理 UTF-8 檔案,就像是在處理 ASCII 檔案一樣。 -
大部分的作業系統也可以直接處理 UTF-8 的檔名,因為主要的區隔資料夾階層的符號是
/
。 -
另一方面,
grep
、sed
、wc
,這類可以接受任意字串當作 input 的工具,就需要一些修改。
-
-
UTF-8 如果以 code point 排序也不用修改:
-
可以參考上面的表,用左邊方式排序與右邊方式相同。
-
這也代表這些指令:
join
、ls
、sort
(沒有其他參數的情況) 不需要對 UTF-8 編碼額外處理。
-
-
UTF-8 沒有 “byte order”:
-
Unicode 定義了 byte order mark (BOM),用來決定 bytes 裡面 bits 讀取的順序;不過這個值不會顯示出任何字元。例如 UTF-16 的
FFFE
。 -
UTF-8 是 byte encoding. 不區分 little endian 或 big endian。
-
部分程式可能會寫 UTF-8 編碼後的 BOM 在檔案的最開頭,不過這沒有必要、也會影響到「沒有考慮到這個標記」的其他程式。
-