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

更新於 發佈於 閱讀時間約 13 分鐘

前言


說明

前情提要

所有電腦儲存的資料基本上就是一堆數字,而文字 (字串) 也是由一堆數字表達而成的。至於要如何定義某些數字代表某些文字,這個過程就稱為編碼。

最直接暴力的方式,就是 Unicode,直接讓把世界上幾乎所有語言用到的「字母」、「文字」都直接對應於一個數字,參見這裡

不過數字有短有長,在資料傳輸的時候,我們自然希望把常用的內容用比較短的數字表示,這樣比較經濟,這個邏輯應該很直覺。至於「到底如何」分類常用與否、或是完成這個編碼,就又是另一個問題。

UTF-8 是其中一個 Unicode 的編碼方式,他把整數 (0 ~ 10FFFF,16 進位表示) 轉成 byte stream (0101…);且它實際上比大家想的單純。


不囉唆先上表

尷尬,方格子這邊 code block 不夠寬......如果有更好的方法請告訴我。

    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


拆開成左右兩部分顯示,要請大家發揮一點想像力假裝他們是同一個表格:

       Unicode code points    

00~7F ( 7 bits)
0080~07FF (11 bits)
0800~FFFF (16 bits)
010000~10FFFF (21 bits)
                                  UTF-8 encoding (binary)

0zzzzzzz
110yyyyy 10zzzzzz
1110xxxx 10yyyyyy 10zzzzzz
11110www 10xxxxxx 10yyyyyy 10zzzzzz

不難看出,總共分成 4 組數字範圍。


第一組 00~7F

  • 十六進位下的 7F 是 127,所以 0 ~ 127 總共對應了 128 個數字。
  • 二進位下,0 ~ 127 會需要 7 個 bits 儲存,這 7 bits 也就對應了表右邊的 7 個 z zzzzzzz
  • 例如 6A (106) 這個數字,轉成二進位是 1101010 填入後得到 01101010 跟 ASCII 完全一樣。這是特意兼容 ASCII 編碼的結果


第二組:0080 ~ 07FF

共 1920 個數字,用來表示各國拼音字母。

維基百科:… 帶有附加符號的拉丁文、希臘文、西里爾字母、亞美尼亞語、希伯來文、阿拉伯文、敘利亞文及它拿字母則需要兩個位元組編碼 (Unicode 範圍由U+0080U+07FF)
    • 0080 ~ 07FF,數字數量是 07FF - 007F = 0780 (十進位 1920);
    • 1920 用二進位表示,最少需要 11 bits (2 的 11 次方是 2048),把這 11 bits 前 5 碼填入表右邊的 yyyyy、後 6 碼填入右邊的 zzzzzz 即代表 encoding 的結果。
    • 例如 026A 這個數字 (十進位 618),轉成 11 bit 是 01001101010,則 encoding 的結果是 11001001 10101010 ,像是這樣:
上表第二組:     

0080~07FF (11 bits) 110yyyyy 10zzzzzz

026A 16 進位 = 01001101010
分成前五、後六 = 01001 101010
yyyyy zzzzzz
-> 11001001 10101010


第三組:0800~FFFF 

仿照一樣的算法會得到 63488 (十進位),不過實際上會用到的只有 61440 個

    • FFFF - 07FF = F800 實際上共 63488 個數字。不過在編碼上,實際會用到的只有
      • 000800 ~ 00D7FF
      • 00E000 ~ 00FFFF 

        這兩段,因此實際會用到的數字數量是 D000 + 2000 = F000 (十進位 61440) 個數字。
    • 61440 用二進位表示,最少需要 16 bits (2 的 16 次方是 65536),前四碼填入表右邊的 xxxx、中間 6 碼填入 yyyyyy、最後 6 碼填入 zzzzzz 即代表 encoding 的結果。
    • 例如 E12A 這個數字 (十進位 57642),換成二進位是 16 bit :1110000100101010,因此 encoding 的結果是 11101110 10000100 10101010
上表第三組:

0800~FFFF (16 bits) 1110xxxx 10yyyyyy 10zzzzzz



E12A 16 進位 = 1110000100101010

分成 4, 6, 6 = 1110 000100 101010
xxxx yyyyyy zzzzzz
-> 11101110 10000100 10101010

(如果這邊可以替文字上色的話會清楚更多;希望方格子可以設計這個 feature。)


我們的中文也落在這個範圍,可以參考 Wiki 中日韓統一表意文字 (Unicode區段)

例如 中文

U+4E2D  U+6587

十進位:​
20013 25991

也就是在 unicode 中, 是第 20013 個字,而 是第 25991 個,夠暴力吧。

每次想到世界上曾經有一些人,把幾乎所有中 (日韓) 文字都跟某個數字關聯起來,就覺得頭皮發麻、枯燥到令人髮指,一方面是數量這麼多,一方面是要怎麼決定優先順序呢?



第四組

  • 類推,累了就不算了😂



性質

完全兼容 ASCII

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

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

理由是只有第一組會是 0 開頭,因此看到 0 就是第一組、也就是 ASCII 字母範圍。

而這個 z (二進位 01111010) 也不會在 UTF-8 中被編碼成 11000001 10111010 (第二組的表示法),也就是不會補零變成 00001111010。因為原則上是要能用最短的編碼表示。

如果編碼失敗,實務上會用 Unicode replacement character (FFFD) 表示,

長這樣:�

是否似曾相識?沒錯,連失敗都有一個數字代表他;FFFD 代表他是第 65533 個字。


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 像是 catcpdiffechoheadtail 等指令都能處理 UTF-8 檔案,就像是在處理 ASCII 檔案一樣。

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

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


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

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

這也代表這些指令:joinlssort (沒有其他參數的情況) 不需要對 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 在檔案的最開頭,不過這沒有必要、也會影響到「沒有考慮到這個標記」的其他程式。


後記

設計這個編碼真的是巧思連連。

想像現在你有一個任務,總共有 6 萬多個數字,要一一把字母跟文字對應起來,同時也要兼容 ASCII、方便搜尋、方便讀取、方便排序。聽起來就不可能。

恭喜你現在也對 UTF-8 多了一些敬意;也恭喜你,看到這裡又變得更宅了一點。



REF




留言
avatar-img
留言分享你的想法!
avatar-img
我是ktc
0會員
5內容數
我最愛的就兩件事:打造網頁服務、以及期貨市場。 因此這裡會紀錄一些網頁服務相關的技術文章;偶而記錄一些海期的心得。
我是ktc的其他內容
2025/09/07
手把手做雜湊!如果你不夠宅是不用特別進來了解,內容偏硬。當年抱著好奇的心態把整個流程順了一遍並且記錄下來,現在搬家轉過來更多是抱著「不能只有我看到」的莫名心態。
2025/09/07
手把手做雜湊!如果你不夠宅是不用特別進來了解,內容偏硬。當年抱著好奇的心態把整個流程順了一遍並且記錄下來,現在搬家轉過來更多是抱著「不能只有我看到」的莫名心態。
2025/09/07
這篇文章彙整了我從 2021 到 2024 年間累積的 Shell Script、Bash 相關的 snippets,同時也是原 blog 搬家後做的去蕪存菁、濃縮的結果。
Thumbnail
2025/09/07
這篇文章彙整了我從 2021 到 2024 年間累積的 Shell Script、Bash 相關的 snippets,同時也是原 blog 搬家後做的去蕪存菁、濃縮的結果。
Thumbnail
2025/09/06
從 Medium 到主機服務商,再到 AWS 自架站,最後選擇搬到方格子,這篇文章記錄了作者在部落格經營過程中的 SEO 困境、技術筆記的邊際效用遞減以及最終選擇方格子作為個人網站平臺的原因。文章涵蓋了技術選型、成本考量、SEO 策略調整,以及作者對內容創作和個人品牌經營的思考。
Thumbnail
2025/09/06
從 Medium 到主機服務商,再到 AWS 自架站,最後選擇搬到方格子,這篇文章記錄了作者在部落格經營過程中的 SEO 困境、技術筆記的邊際效用遞減以及最終選擇方格子作為個人網站平臺的原因。文章涵蓋了技術選型、成本考量、SEO 策略調整,以及作者對內容創作和個人品牌經營的思考。
Thumbnail
看更多