知識 · 文字、資料與資安
Unicode 和 UTF-8 有什麼不同?
簡答
Unicode 是字元集:它為每個字元指定一個稱為碼位(code point)的編號,例如「é」是 U+00E9。UTF-8 是編碼方式:把這些編號存成位元組的規則。ASCII 字元用 1 個位元組,其他字元用 2 到 4 個位元組,所以「é」會變成 C3 A9。Unicode 決定是哪個字元,UTF-8 決定它如何寫入磁碟或透過網路傳送。
先有碼位,再有位元組
Unicode 定義的編碼空間從 U+0000 到 U+10FFFF,可容納 1,114,112 個碼位,分成 17 個平面,每個平面 65,536 個。其中超過 150,000 個已分配給字元,涵蓋現代與古代文字、符號和表情符號。碼位的寫法是 U+ 後面接至少四位十六進位數字。
碼位只是一個數字。要儲存或傳送文字,就得把它轉成位元組,而方法不只一種:UTF-8、UTF-16 和 UTF-32 都是同一套 Unicode 字元的編碼方式。
| 字元 | 碼位 | UTF-8 位元組 | UTF-16(大端序) |
|---|---|---|---|
| A | U+0041 | 41 | 00 41 |
| é | U+00E9 | C3 A9 | 00 E9 |
| € | U+20AC | E2 82 AC | 20 AC |
| 日 | U+65E5 | E6 97 A5 | 65 E5 |
| 😀 | U+1F600 | F0 9F 98 80 | D8 3D DE 00 |
UTF-8 如何決定用 1、2、3 或 4 個位元組
UTF-8 規範於 RFC 3629,是一種變長編碼。第一個位元組的開頭位元表示後面還有幾個位元組,而每個後續位元組都以 10 開頭。這讓資料流能自我同步:解碼器即使從字元中間開始讀,也能往後跳到下一個起始位元組。
| 碼位範圍 | 位元組數 | 位元模式 |
|---|---|---|
| U+0000 – U+007F | 1 | 0xxxxxxx |
| U+0080 – U+07FF | 2 | 110xxxxx 10xxxxxx |
| U+0800 – U+FFFF | 3 | 1110xxxx 10xxxxxx 10xxxxxx |
| U+10000 – U+10FFFF | 4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
計算範例:「é」是 U+00E9,二進位為 000 1110 1001,需要 2 個位元組的模式。高 5 位元 00011 填入 110xxxxx,得到 11000011(C3);低 6 位元 101001 填入 10xxxxxx,得到 10101001(A9)。
由於前 128 個碼位編碼後與 ASCII 的單一位元組完全相同,任何合法的 ASCII 檔案本身就是合法的 UTF-8。這種向下相容性正是 UTF-8 成為網路預設編碼的主要原因,目前約 98% 的網站使用它。
UTF-8、UTF-16 與 UTF-32 比較
| 編碼 | 單位大小 | 每字元位元組數 | 常見於 |
|---|---|---|---|
| UTF-8 | 8 位元 | 1–4 | 網頁、JSON、Linux 與 macOS 檔案、多數 API |
| UTF-16 | 16 位元 | 2 或 4 | JavaScript 字串、Java、Windows API |
| UTF-32 | 32 位元 | 固定 4 | 需要固定寬度的內部處理 |
UTF-16 會把 U+FFFF 以上的字元存成由兩個 16 位元單位組成的代理對(surrogate pair)。這就是為什麼在 JavaScript 中 "😀".length 是 2:這個語言計算的是 UTF-16 單位,而不是字元。改用 [..."😀"].length 迭代則會得到 1。
英文文字用 UTF-8 最精簡。若文字以中文、日文或韓文為主,UTF-16 可能更小,因為這些字元在 UTF-8 中佔 3 個位元組,在 UTF-16 中只佔 2 個。
位元組順序記號(BOM)
BOM 是放在檔案最開頭的字元 U+FEFF。在 UTF-16 和 UTF-32 中,它告訴讀取端位元組是大端序還是小端序。在 UTF-8 中它編碼為 EF BB BF,只是一個簽章,因為 UTF-8 沒有位元組順序之分。
Unicode 的常見問答指出,UTF-8 BOM 既非必要,也不建議使用。但有些 Windows 編輯器還是會加上它,結果讓 shell 指令碼(#! 那行不再是第一行)、CSV 標題列,以及沒預期到它的 JSON 剖析器出錯。如果檔案開頭出現 ,那就是被當成 Windows-1252 讀取的 UTF-8 BOM。
文字為什麼會變成 é 或 ���
亂碼(mojibake)會在解碼使用的編碼和寫入時不同時出現:
é變成é:UTF-8 位元組C3 A9被當成 Windows-1252 或 Latin-1 讀取,C3對應到Ã,A9對應到©。�(U+FFFD,替代字元):解碼器遇到不合法的 UTF-8 位元組,常見於把 Latin-1 文字當成 UTF-8 讀取。- 資料庫裡出現問號:欄位或連線使用的字元集裝不下該字元,寫入時就被替換掉,資料已經不在了。
解決方法是在每個交界處都宣告編碼:HTML 中的 <meta charset="utf-8">、HTTP 標頭中的 Content-Type: text/html; charset=utf-8,資料庫欄位和連線也都用 UTF-8。MySQL 請使用 utf8mb4;較舊的 utf8 字元集每個字元最多只存 3 個位元組,會拒收表情符號。
常見問題
UTF-8 和 Unicode 一樣嗎?
不一樣。Unicode 是字元及其編號的清單,UTF-8 是把這些編號存成位元組的其中一種方式。大家常說「Unicode」,其實指的是 UTF-8 文字。
UTF-8 中一個字元佔幾個位元組?
1 到 4 個。英文字母佔 1 個位元組,多數歐洲帶重音的字母佔 2 個,多數中日韓文字佔 3 個,表情符號佔 4 個。
UTF-8 能表示所有字元嗎?
可以。UTF-8 能編碼從 U+0000 到 U+10FFFF 的所有 Unicode 碼位,所以 Unicode 定義的任何字元都能用 UTF-8 寫出。
存檔時該加 BOM 嗎?
UTF-8 通常不必。它是選用的,對位元組順序沒有作用,還會讓預期前幾個位元組就是內容的工具出錯。不過某些舊版 Excel 需要它,才能正確開啟 UTF-8 的 CSV 檔。