UTF-8 天天用,但你知道他到底怎麼編碼的嗎?



前言

  • 先前讀到一篇文章介紹 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~78 個號碼、0~F16 個號碼,因此有 8 * 16 = 128 個數字 (7F 就是 127,加上 0128 個)

    • 將這個數字轉為二進位後,會得到 7 bit (27 次方是 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 (211 次方是 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 ~ 00D7FF00E000 ~ 00FFFF 兩段,因此實際會用到的數字是 (D7FF - 7FF) + (FFFF - DFFF) = D000 + 2000 = F000 (十進位 61440) 個數字。

    • 61440 用二進位表示,最少需要 16 bit (216 次方是 65536),前四碼填入表右邊的 xxxx、中間 6 碼填入 yyyyyy、最後 6 碼填入 zzzzzz 即代表 encoding 的結果。

    • 例如 E12A 這個數字 (十進位 57642),換成二進位是 16 bit :1110000100101010,因此 encoding 的結果是 11101110 10000100 10101010

  • 第四組類推,就不算了😂


性質

  1. 所有的 ASCII 檔案就是 UTF-8 檔案,完全兼容。參考上面第一組編碼的說明。

  2. 在 UTF-8 編碼中看到 ASCII bytes 可以確保是完全一樣的字。例如在 UTF-8 檔案中看到 0x7A (01111010),可以確認他一定代表字母 z、ASCII bytes 也不會被其他 UTF-8 編碼覆蓋。

  3. z 二進位 01111010 不會在 UTF-8 中被編碼成 11000001 10111010 (第二組的表示法),也就是不會編成 00001111010。原則上是要能用最短的編碼表示;如果編碼失敗,實務上會用 Unicode replacement character (FFFD) 表示,長這樣:� (似曾相識?)

  4. UTF-8 is** self-synchronizing**:

    • 仔細觀察上表的右手邊,除了開頭第一組之外,其他組的內部、後面跟隨的 bytes 的都是 **10**______ 開頭。

    • 例如第三組 1110xxxx **10**yyyyyy **10**zzzzzz

    • 這些 bytes 稱作 continuation byte,代表是同一組 encoding 的字元,也就能快速找到下一個字的開頭 — 下一組 bytes 如果不是 10______ ,即是另一個字的開頭。

  5. 字串搜尋 (substring search) 只是 bytes string search:從以上性質可知,只要對用一組組 bytes (8-bits) 比對即可完成字串搜尋的任務。

  6. 大部分的程式,如果能正常處理 8-bits 檔案,就成正常處理 UTF-8 編碼檔案:

    • 之所以說「大部分」是因為,如果某些程式會把這些 bytes 拆開視為個別字元的話就會出錯;不過現在已經很少程式會有這種狀況了。更常見的情況是以換行 \n 或是空白當作字元分隔的符號,這與 ASCII 的編碼是相同的。也因此 Unix Tool 像是 catcpdiffechoheadtail 等指令都能處理 UTF-8 檔案,就像是在處理 ASCII 檔案一樣。

    • 大部分的作業系統也可以直接處理 UTF-8 的檔名,因為主要的區隔資料夾階層的符號是 /

    • 另一方面,grepsedwc,這類可以接受任意字串當作 input 的工具,就需要一些修改。

  7. UTF-8 如果以 code point 排序也不用修改:

    • 可以參考上面的表,用左邊方式排序與右邊方式相同。

    • 這也代表這些指令:joinlssort (沒有其他參數的情況) 不需要對 UTF-8 編碼額外處理。

  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 在檔案的最開頭,不過這沒有必要、也會影響到「沒有考慮到這個標記」的其他程式。




REF

Licensed under CC BY-NC-SA 4.0
最後更新 2024-05-16 02:51

主題 StackJimmy 設計