UTF-8

**UTF-8**（**8-bit Unicode Transformation Format**）是一種針對 Unicode 的可變長度字元編碼，也是一種字首碼。它可以用一至四個位元組對 Unicode 字元集中的所有有效編碼點進行編碼，屬於 Unicode 標準的一部分，最初由肯・湯普遜和羅布・派克提出。由於較小值的編碼點一般使用頻率較高，直接使用 Unicode 編碼效率低下，大量浪費記憶體空間。UTF-8 就是為了解決向下相容 ASCII 碼而設計，Unicode 中前 128 個字元，使用與 ASCII 碼相同的二進位值的單個位元組進行編碼，而且字面與 ASCII 碼的字面一一對應，這使得原來處理 ASCII 字元的軟體無須或只須做少部份修改，即可繼續使用。因此，它逐漸成為電子郵件、網頁及其他儲存或傳送文字優先採用的編碼方式。

自 2009 年以來，UTF-8 一直是全球資訊網的最主要的編碼形式（對所有，而不僅是 Unicode 範圍內的編碼）（並由 WHATWG 宣布為強制性的「適用於所有事物 (for all things)」，截止到 2019 年 11 月， 在所有網頁中，UTF-8 編碼應用率高達 94.3%（其中一些僅是 ASCII 編碼，因為它是 UTF-8 的子集），而在排名最高的 1000 個網頁中占 96％。 第二熱門的多位元組編碼方式 Shift JIS 和 GB 2312 分別具有 0.3％和 0.2％的占有率。Internet 郵件聯盟（ Internet Mail Consortium, IMC）建議所有電子郵件程式都能夠使用 UTF-8 展示和建立郵件， W3C 建議 UTF-8 作為 XML 檔案和 HTML 檔案的預設編碼方式。網際網路工程工作小組（IETF）要求所有網際網路協定都必須支援 UTF-8 編碼。網際網路郵件聯盟（IMC）建議所有電子郵件軟體都支援 UTF-8 編碼。

## 歷史

1992 年初，為建立良好的位元組串編碼系統以供多位元組字元集使用，開始了一個正式的研究。ISO/IEC 10646 的初稿中有一個非必須的附錄，名為 UTF。當中包含了一個供 32 位元的字元使用的位元組串編碼系統。這個編碼方式的效能並不令人滿意，但它提出了將 0-127 的範圍保留給 ASCII 以相容舊系統的概念。

1992 年 7 月，X/Open 委員會 XoJIG 開始尋求一個較佳的編碼系統。Unix 系統實驗室（USL）的 Dave Prosser 為此提出了一個編碼系統的建議。它具備可更快速實作的特性，並引入一項新的改進。其中，7 位元的 ASCII 符號只代表原來的意思，所有多位元組序列則會包含第 8 位元的符號，也就是所謂的最高有效位元。

1992 年 8 月，這個建議由 IBMX/Open 的代表流傳到一些感興趣的團體。與此同時，貝爾實驗室九號計畫作業系統工作小組的肯・湯普遜對這編碼系統作出重大的修改，讓編碼可以自我同步，使得不必從字串的開首讀取，也能找出字元間的分界。1992 年 9 月 2 日，肯・湯普遜和羅勃・派克一起在美國新澤西州一架餐車的餐桌墊上描繪出此設計的要點。接下來的日子，Pike 及湯普遜將它實現，並將這編碼系統完全應用在九號計畫當中，及後他將有關成果回饋 X/Open。

1993 年 1 月 25-29 日的在聖地牙哥舉行的 USENIX 會議首次正式介紹 UTF-8。

自 1996 年起，微軟的 CAB（MS Cabinet）規格在 UTF-8 標準正式落實前就明確容許在任何地方使用 UTF-8 編碼系統。但有關的編碼器實際上從來沒有實作這方面的規格。

## 結構

UTF-8 使用一至六個位元組為每個字元編碼（儘管如此，2003 年 11 月 UTF-8 被 RFC 3629 重新規範，只能使用原來 Unicode 定義的區域，U+0000 到 U+10FFFF，也就是說最多四個位元組）：

1. 128 個 US-ASCII 字元只需一個位元組編碼（Unicode 範圍由 U+0000 至 U+007F）。
2. 帶有附加符號的拉丁文、希臘文、西里爾字母、亞美尼亞語、希伯來文、阿拉伯文、敘利亞文及它拿字母則需要兩個位元組編碼（Unicode 範圍由 U+0080 至 U+07FF）。
3. 其他基本多文種平面（BMP）中的字元（這包含了大部分常用字，如大部分的漢字）使用三個位元組編碼（Unicode 範圍由 U+0800 至 U+FFFF）。
4. 其他極少使用的 Unicode 輔助平面的字元使用四至六位元組編碼（Unicode 範圍由 U+10000 至 U+1FFFFF 使用四位元組，Unicode 範圍由 U+200000 至 U+3FFFFFF 使用五位元組，Unicode 範圍由 U+4000000 至 U+7FFFFFFF 使用六位元組）。

對上述提及的第四種字元而言，UTF-8 使用四至六個位元組來編碼似乎太耗費資源了。但 UTF-8 對所有常用的字元都可以用三個位元組表示，而且它的另一種選擇，UTF-16 編碼，對前述的第四種字元同樣需要四個位元組來編碼，所以要決定 UTF-8 或 UTF-16 哪種編碼比較有效率，還要視所使用的字元的分佈範圍而定。不過，如果使用一些傳統的壓縮系統，比如 DEFLATE，則這些不同編碼系統間的的差異就變得微不足道了。若顧及傳統壓縮演算法在壓縮較短文字上的效果不大，可以考慮使用 Unicode 標準壓縮格式（SCSU）。

## 描述

目前有好幾份關於 UTF-8 詳細規格的檔案，但這些檔案在定義上有些許的不同：

* RFC 3629 / STD 63（2003），這份檔案制定了 UTF-8 是標準的網際網路協定元素
* _第四版，The Unicode Standard_，§3.9－§3.10（2003）
* ISO/IEC 10646-1:2000 附加檔案 D（2000）

它們取代了以下那些被淘汰的定義：

* ISO/IEC 10646-1:1993 修正案 2／附加檔案 R（1996）
* _第二版，The Unicode Standard_，附錄 A（1996）
* RFC 2044（1996）
* RFC 2279（1998）
* _第三版，The Unicode Standard_，§2.3（2000）及勘誤表 #1：UTF-8 Shortest Form（2000）
* _Unicode Standard 附加檔案 #27: Unicode 3.1_（2001）

事實上，所有定義的基本原理都是相同的，它們之間最主要的不同是支援的字元範圍及無效輸入的處理方法。

Unicode 字元的位元被分割為數個部分，並分配到 UTF-8 的位元組串中較低的位元的位置。在 U+0080 的以下字元都使用內含其字元的單位元組編碼。這些編碼正好對應 7 位元的 ASCII 字元。在其他情況，有可能需要多達 4 個字元組來表示一個字元。這些多位元組的最高有效位元會設定成 1，以防止與 7 位元的 ASCII 字元混淆，並保持標準的位元組主導字串運作順利。

　
: **Note 1** Unicode 在範圍 D800-DFFF 中不存在任何字元，基本多文種平面中約定了這個範圍用於 UTF-16 擴展標識輔助平面（兩個 UTF-16 表示一個輔助平面字元）。當然，任何編碼都是可以被轉換到這個範圍，但在 unicode 中他們並不代表任何合法的值。

例如，希伯來語字母 aleph（א）的 Unicode 代碼是 U+05D0，按照以下方法改成 UTF-8：

* 它屬於 U+0080 到 U+07FF 區域，這個表說明它使用雙位元組，_110_yyyyy _10_zzzzzz.
* 十六進位的 0x05D0 換算成二進位就是 101-1101-0000.
* 這 11 位數按順序放入 "y" 部分和 "z" 部分：110**10111** 10**010000**.
* 最後結果就是雙位元組，用十六進位寫起來就是 0xD7 0x90，這就是這個字元 aleph（א）的 UTF-8 編碼。

所以開始的 128 個字元（US-ASCII）只需一位元組，接下來的 1920 個字元需要雙位元組編碼，包括帶附加符號的拉丁字母，希臘字母，西里爾字母，科普特語字母，亞美尼亞語字母，希伯來文字母和阿拉伯字母的字元。基本多文種平面中其餘的字元使用三個位元組，剩餘字元使用四個位元組。

根據這種方式可以處理更大數量的字元。原來的規範允許長達 6 位元組的序列，可以覆蓋到 31 位元（通用字元集原來的極限）。儘管如此，2003 年 11 月 UTF-8 被 RFC 3629 重新規範，只能使用原來 Unicode 定義的區域，U+0000 到 U+10FFFF。根據這些規範，以下位元組值將無法出現在合法 UTF-8 序列中：

## UTF-8 編碼位元組含義

* 對於 UTF-8 編碼中的任意位元組 B，如果 B 的第一位為 0，則 B 獨立的表示一個字元 (ASCII 碼)；
* 如果 B 的第一位為 1，第二位為 0，則 B 為一個多位元組字元中的一個位元組 (非 ASCII 字元)；
* 如果 B 的前兩位為 1，第三位為 0，則 B 為兩個位元組表示的字元中的第一個位元組；
* 如果 B 的前三位為 1，第四位為 0，則 B 為三個位元組表示的字元中的第一個位元組；
* 如果 B 的前四位為 1，第五位為 0，則 B 為四個位元組表示的字元中的第一個位元組；

因此，對 UTF-8 編碼中的任意位元組，根據第一位，可判斷是否為 ASCII 字元；根據前二位，可判斷該位元組是否為一個字元編碼的第一個位元組；根據前四位（如果前兩位均為 1），可確定該位元組為字元編碼的第一個位元組，並且可判斷對應的字元由幾個位元組表示；根據前五位（如果前四位為 1），可判斷編碼是否有錯誤或資料傳輸過程中是否有錯誤。

## 設計 UTF-8 的理由

UTF-8 的設計有以下的多字元組序列的特質：

* 單位元組字元的最高有效位元永遠為 0。
* 多位元組序列中的首個字元組的幾個最高有效位元決定了序列的長度。最高有效位為 `110` 的是 2 位元組序列，而 `1110` 的是三位元組序列，如此類推。
* 多位元組序列中其餘的位元組中的首兩個最高有效位元為 `10`。

UTF-8 的這些特質，保證了一個字元的位元組序列不會包含在另一個字元的位元組序列中。這確保了以位元組為基礎的部份字串比對（sub-string match）方法可以適用於在文字中搜尋字或詞。有些比較舊的可變長度 8 位元編碼（如 Shift JIS）沒有這個特質，故字串比對的演算法變得相當複雜。雖然這增加了 UTF-8 編碼的字串的資訊冗餘，但是利多於弊。另外，資料壓縮並非 Unicode 的目的，所以不可混為一談。即使在傳送過程中有部份位元組因錯誤或干擾而完全遺失，還是有可能在下一個字元的起點重新同步，令受損範圍受到限制。

另一方面，由於其位元組序列設計，如果一個疑似為字串的序列被驗證為 UTF-8 編碼，那麼我們可以有把握地說它是 UTF-8 字串。一段兩位元組隨機序列碰巧為合法的 UTF-8 而非 ASCII 的機率為 32 分 1。對於三位元組序列的機率為 256 分 1，對更長的序列的機率就更低了。

## UTF-8 的編碼方式

UTF-8 是 UNICODE 的一種變長度的編碼表達方式〈一般 UNICODE 為雙位元組（指 UCS2）〉，它由肯・湯普遜（Ken Thompson）於 1992 年建立，現在已經標準化為 RFC 3629。UTF-8 就是以 8 位元為單元對 UCS 進行編碼，而 UTF-8 不使用大尾序和小尾序的形式，每個使用 UTF-8 儲存的字元，除了第一個位元組外，其餘位元組的頭兩個位元都是以 "10" 開始，使文字處理器能夠較快地找出每個字元的開始位置。

但為了與以前的 ASCII 碼相容（ASCII 為一個位元組），因此 UTF-8 選擇了使用可變長度位元組來儲存 Unicode：

（注意：不論是 Unicode (Table 3.7) ，還是 ISO 10646 (10.2 UTF-8) ，目前都只規定了最高碼位是 0x10FFFF 的字元的編碼。下表中表示大於 0x10FFFF 的 UTF-8 編碼是不符合標準的。）

* 在 ASCII 碼的範圍，用一個位元組表示，超出 ASCII 碼的範圍就用位元組表示，這就形成了我們上面看到的 UTF-8 的表示方法，這樣的好處是當 UNICODE 檔案中只有 ASCII 碼時，儲存的檔案都為一個位元組，所以就是普通的 ASCII 檔案無異，讀取的時候也是如此，所以能與以前的 ASCII 檔案相容。
* 大於 ASCII 碼的，就會由上面的第一位元組的前幾位表示該 unicode 字元的長度，比如 110xxxxx 前三位的二進位表示告訴我們這是個 2BYTE 的 UNICODE 字元；1110xxxx 是個三位的 UNICODE 字元，依此類推；xxx 的位置由字元編碼數的二進製表示的位填入。越靠右的 x 具有越少的特殊意義。只用最短的那個足夠表達一個字元編碼數的多位元組串。注意在多位元組串中，第一個位元組的開頭 "1" 的數目就是整個串中位元組的數目。

ASCII 字母繼續使用 1 位元組儲存，重音文字、希臘字母或西里爾字母等使用 2 位元組來儲存，而常用的漢字就要使用 3 位元組。輔助平面字元則使用 4 位元組。

在 UTF-8+BOM 格式檔案的開首，很多時都放置一個 U+FEFF 字元（UTF-8 以 EF,BB,BF 代表），以顯示這個文字檔案是以 UTF-8 編碼。

## UTF-8 的特性

* UCS 字元 U+0000 到 U+007F（ASCII）被編碼為位元組 0x00 到 0x7F（ASCII 相容），這也意味著只包含 7 位 ASCII 字元的檔案在 ASCII 和 UTF-8 兩種編碼方式下是一樣的。
* 所有 > U+007F 的 UCS 字元被編碼為一個多個位元組的串，每個位元組都有標記位集。因此，ASCII 位元組（0x00-0x7F）不可能作為任何其他字元的一部分。
* 表示非 ASCII 字元的多位元組串的第一個位元組總是在 0xC0 到 0xFD 的範圍裡，並指出這個字元包含多少個位元組。多位元組串的其餘位元組都在 0x80 到 0xBF 範圍裡，這使得重新同步非常容易，並使編碼無國界，且很少受丟失位元組的影響。
* 可以編入所有可能的 231 個 UCS 代碼
* UTF-8 編碼字元理論上可以最多到 6 個位元組長，然而 16 位元 BMP 字元最多只用到 3 位元組長。
* Bigendian UCS-4 位元組串的排列順序是預定的。
* 位元組 0xFE 和 0xFF 在 UTF-8 編碼中從未用到，同時，UTF-8 以位元組為編碼單元，它的位元組順序在所有系統中都是一様的，沒有位元組序的問題，也因此它實際上並不需要 BOM。
* 與 UTF-16 或其他 Unicode 編碼相比，對於不支援 Unicode 和 XML 的系統，UTF-8 更不容易造成問題。

## UTF-8 編碼的優點

總體來說，在 Unicode 字串中不可能由碼點數量決定顯示它所需要的長度，或者顯示字串之後在文字緩衝區中游標應該放置的位置；組合字元、變寬字型、不可列印字元和從右至左的文字都是其歸因。

所以儘管在 UTF-8 字串中字元數量與碼點數量的關係比 UTF-32 更為複雜，在實際中很少會遇到有不同的情形。

更詳細的說，UTF-8 編碼具有以下幾點優點：

* ASCII 是 UTF-8 的一個子集。因為一個純 ASCII 字串也是一個合法的 UTF-8 字串，所以現存的 ASCII 文字不需要轉換。為傳統的擴充 ASCII 字元集設計的軟體通常可以不經修改或很少修改就能與 UTF-8 一起使用。
* 使用標準的面向位元組的排序常式對 UTF-8 排序將產生與基於 Unicode 代碼點排序相同的結果。（儘管這只有有限的有用性，因為在任何特定語言或文化下都不太可能有仍可接受的文字排列順序。）
* UTF-8 和 UTF-16 都是可延伸標記式語言文件的標準編碼。所有其它編碼都必須通過顯式或文字聲明來指定。[1]（頁面存檔備份，存於網際網路檔案館）
* 任何面向位元組的字串搜尋演算法都可以用於 UTF-8 的資料（只要輸入僅由完整的 UTF-8 字元組成）。但是，對於包含字元記數的正規表示式或其它結構必須小心。
* UTF-8 字串可以由一個簡單的演算法可靠地辨識出來。就是，一個字串在任何其它編碼中表現為合法的 UTF-8 的可能性很低，並隨字串長度增長而減小。舉例說，字元值 C0,C1,F5 至 FF 從來沒有出現。為了更好的可靠性，可以使用正規表示式來統計非法過長和替代值（可以檢視 W3 FAQ: Multilingual Forms （頁面存檔備份，存於網際網路檔案館）上的驗證 UTF-8 字串的正規表示式）。
* 與 UCS-2 的比較：ASCII 轉換成 UCS-2，在編碼前插入一個 0x0。用這些編碼，會含括一些控制符，比如 " 或 '/'，這在 UNIX 和一些 C 函式中，將會產生嚴重錯誤。因此可以肯定，UCS-2 不適合作為 Unicode 的外部編碼，也因此誕生了 UTF-8。

## UTF-8 編碼的缺點

### 編寫不良的解析器

如果一個 UTF-8 解析器寫得很差（並且與當前標準的版本不相容），那麼它接收到一些偽 UTF-8 時會將其轉換成看似正確實則錯誤的 Unicode 輸出。處理八位表示的校驗常式可能遺漏一些資訊。

### 不利於正規表示式檢索

正規表示式可以進行很多進階的英文模糊檢索。例如，[a-h] 表示 a 到 h 間所有字母。

同樣 GBK 編碼的中文也可以這樣利用正規表示式，比如在只知道一個字的讀音而不知道怎麼寫的情況下，也可用正規表示式檢索，因為 GBK 編碼是按讀音排序的。但是 Unicode 漢字不是按讀音排序的，所以不利於用正規表示式檢索。雖然正規表示式檢索並未考慮中文的多音字，但是由於中文的多音字數量不多，不少多音字還是同音不同調類型的多音字，所以大多數情況下正規表示式檢索是還可以接受的。不過 Unicode 漢字按部首排序，因此在只知道一個字的部首而不知道如何發音的情況下，UTF-8 可用正規表示式檢索而 GBK 不行。

### 其他

UTF-8 的 ASCII 字元只占用一個位元組，比較節省空間，但是更多字元的 UTF-8 編碼佔用的空間就要多出 1/2，特別是中文、日文和韓文（CJK）這樣的方塊文字，它們大多需要三個位元組。

## UTF-8 的衍生物

### Windows

雖然不是標準，但許多 Windows 程式（包括 Windows 記事本）在 UTF-8 編碼的檔案的開首加入一段位元組串 `EF BB BF`。這是位元組順序記號 `U+FEFF` 的 UTF-8 編碼結果。對於沒有預期要處理 UTF-8 的文字編輯器和瀏覽器會顯示成 ISO-8859-1 字串 `ï»¿`。

### Posix 系統

Posix 系統明確不建議使用位元組序遮罩 `EF BB BF`。因為很多文字檔案期望以 「#!」（Shebang）開頭指示要執行的程式。Linux 系統選擇使用 Unicode 規範形式 Normalization Form C（NFC），即優先使用預組裝字元（precomposed character）而非組合字元序列（combining character sequence）。

2002 年 9 月發布的 Red Hat Linux 8.0 才開始正式把大多數區域設定的預設編碼設為 UTF-8。此前是各種語言的但位元組編碼為主。2004 年 9 月 SuSE Linux 9.1 開始，預設編碼遷移為 UTF-8。

字串處理時，使用 UTF-8 或 locale 依賴的多位元組編碼情形，比使用 C 語言 wchar\_t 的寬字元固定寬度編碼，要慢 1 至 2 個數量級。

### Java

在通常用法下，Java 程式語言在通過 `InputStreamReader` 和 `OutputStreamWriter` 讀取和寫入串的時候支援標準 UTF-8。但是，Java 也支援一種非標準的變體 UTF-8，供物件的序列化，Java 本地介面和在 class 檔案中的嵌入常數時使用的 `modified UTF-8`。

#### 變種 UTF-8

標準和變種的 UTF-8 有兩個不同點。第一，空字元（null character，U+0000）使用雙位元組的 0xc0 0x80，而不是單位元組的 0x00。這保證了在已編碼字串中沒有嵌入空位元組。因為 C 語言等語言程式中，單位元組空字元是用來標誌字串結尾的。當已編碼字串放到這樣的語言中處理，一個嵌入的空字元將把字串一刀兩斷。

第二個不同點是基本多文種平面之外字元的編碼的方法。在標準 UTF-8 中，這些字元使用 4 位元組形式編碼，而在修正的 UTF-8 中，這些字元和 UTF-16 一樣首先表示為代理對（surrogate pairs），然後再像 CESU-8 那樣按照代理對分別編碼。這樣修正的原因更是微妙。Java 中的字元為 16 位元長，因此一些 Unicode 字元需要兩個 Java 字元來表示。語言的這個性質蓋過了 Unicode 的增補平面的要求。儘管如此，為了要保持良好的向下相容、要改變也不容易了。這個修正的編碼系統保證了一個已編碼字串可以一次編為一個 UTF-16 碼，而不是一次一個 Unicode 碼點。不幸的是，這也意味著 UTF-8 中需要 4 位元組的字元在變種 UTF-8 中變成需要 6 位元組。

因為變種 UTF-8 並**不是** UTF-8，所以使用者在交換資訊和使用網際網路的時候需要特別注意不要誤把變種 UTF-8 當成 UTF-8 資料。

### Mac OS X

Mac OS X 作業系統使用正式分解萬國碼（canonically decomposed Unicode），在檔案系統中使用 UTF-8 編碼進行檔案命名，這做法通常被稱為 UTF-8-MAC。正式分解萬國碼中，預組合字元是被禁止使用的，必須以組合字元取代。

這種方法使分類變得非常簡單，但是會搞混那些使用預組合字元為標準、組合字元用來顯示特殊字元的軟體。Mac 系統的這種 NFD 資料是萬國碼規格化（Unicode normalization）的一種格式。而其他系統，包括 Windows 和 Linux，使用萬國碼規範的 NFC 形式，也是 W3C 標準使用的形式。所以通常 NFD 資料必須轉換成 NFC 才能被其他平台或者網路使用。

蘋果開發者專區有關於此問題的討論：Apple Q&A 1173 （頁面存檔備份，存於網際網路檔案館）。

### MySQL

MySQL 字元編碼集中有兩套 UTF-8 編碼實現：「utf8」和「utf8mb4」，其中「utf8」是一個字最多占據 3 位元組空間的編碼實現；而「utf8mb4」則是一個字最多占據 4 位元組空間的編碼實現，也就是 UTF-8 的完整實現。這是由於 MySQL 在 4.1 版本開始支援 UTF-8 編碼（當時參考 UTF-8 草案版本為 RFC 2279）時，為 2003 年，並且在同年 9 月限制了其實現的 UTF-8 編碼的空間占用最多為 3 位元組，而 UTF-8 正式形成標準化文件（RFC 3629）是其之後。限制 UTF-8 編碼實現的編碼空間占用一般被認為是考慮到資料庫檔案設計的相容性和讀取最佳化，但實際上並沒有達到目的，而且在 UTF-8 編碼開始出現需要存入非基本多文種平面的 Unicode 字元（例如 emoji 字元）時導致無法存入（由於 3 位元組的實現只能存入基本多文種平面內的字元）。直到 2010 年在 5.5 版本推出「utf8mb4」來代替、「utf8」重新命名為「utf8mb3」並調整「utf8」為「utf8mb3」的別名，並不建議使用舊「utf8」編碼，以此修正遺留問題。

## 參閱

## 參考文獻

## 外部連結