前言
-
先前讀到一篇文章介紹
UTF-8
,突然發現時常轉換來轉換去,卻不知道他的編碼規則究竟是什麼。了解一下順便紀錄一篇。 -
本篇節錄、翻譯自 UTF-8: Bits, Bytes, and Benefits。
說明
Code point → Encoding
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
兩位數字,表示:0
7
共8
個號碼、0
F
共16
個號碼,因此有8 * 16 = 128
個數字 (7F
就是127
,加上0
共128
個) -
將這個數字轉為二進位後,會得到
7
bit (2
的7
次方是128
),填入表右邊zzzzzzz
代表的位置即為 encoding 的結果。 -
例如
6A
這個數字 (十進位106
),轉成 7 bit 二進位是:1101010
,所以 encoding 的結果是01101010
;跟 ASCII 完全一樣。
-
-
第二組:
0080
~07FF
,共1920
個數字,用來表示各國拼音字母。(維基百科:… 帶有附加符號的拉丁文、希臘文、西里爾字母、亞美尼亞語、希伯來文、阿拉伯文、敘利亞文及它拿字母則需要兩個位元組編碼 (Unicode 範圍由U+0080 至 U+07FF))-
0080
~07FF
,數字數量是07FF - 007F = 0780
(十進位1920
);07FF
表示的數量為8 * 16 * 16 = 2048
(0
7
共 8 個數字、0
F
共 16 個數字),因此是2048 -128 = 1920
個數字。 -
1920
用二進位表示,最少需要11
bit (2
的11
次方是2048
),把這11
bits 前 5 碼填入表右邊的yyyyy
、後 6 碼填入右邊的zzzzzz
即代表 encoding 的結果。 -
例如
026A
這個數字 (十進位618
),轉成11
bit 是01001101010
,則 encoding 的結果是11001001 10101010
-
-
第三組:
0800
~FFFF
仿照一樣的算法會得到63488
,不過實際上會用到的只有**61440**
個-
FFFF - 07FF = F800
實際上共63488
個數字。不過在編碼上,實際會用到的只有000800
~00D7FF
、00E000
~00FFFF
兩段,因此實際會用到的數字是(D7FF - 7FF) + (FFFF - DFFF) = D000 + 2000 = F000
(十進位 61440) 個數字。 -
61440
用二進位表示,最少需要16
bit (2
的16
次方是65536
),前四碼填入表右邊的xxxx
、中間 6 碼填入yyyyyy
、最後 6 碼填入zzzzzz
即代表 encoding 的結果。 -
例如
E12A
這個數字 (十進位57642
),換成二進位是16
bit :1110000100101010
,因此 encoding 的結果是1110
111010
00010010101010
。
-
-
第四組類推,就不算了😂
性質
-
所有的 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 **10**yyyyyy **10**zzzzzz
-
這些 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 在檔案的最開頭,不過這沒有必要、也會影響到「沒有考慮到這個標記」的其他程式。
-