UTF-16

**UTF-16** 是 Unicode 字元編碼五層次模型的第三層：字元編碼表（Character Encoding Form，也稱為 "storage format"）的一種實現方式。即把 Unicode 字元集的抽象碼位對映為 16 位元長的整數（即碼元）的序列，用於資料儲存或傳遞。Unicode 字元的碼位，需要 1 個或者 2 個 16 位元長的碼元來表示，因此這是一個變長表示。

UTF 是 "Unicode/UCS Transformation Format" 的首字母縮寫，即把 Unicode 字元轉換為某種格式之意。UTF-16 正式定義於 ISO/IEC 10646-1 的附錄 C，而 RFC2781 也定義了相似的做法。

## UTF-16 描述

Unicode 的編碼空間從 U+0000 到 U+10FFFF，共有 1,112,064 個碼位（code point）可用來對映字元。Unicode 的編碼空間可以劃分為 17 個平面（plane），每個平面包含 216（65,536）個碼位。17 個平面的碼位可表示為從 U+xx0000 到 U+xxFFFF，其中 xx 表示十六進位值從 0016 到 1016，共計 17 個平面。第一個平面稱為**基本多語言平面**（Basic Multilingual Plane, **BMP**），或稱第零平面（Plane 0），其他平面稱為**輔助平面**（Supplementary Planes）。基本多語言平面內，從 U+D800 到 U+DFFF 之間的碼位區段是永久保留不對映到 Unicode 字元。UTF-16 就利用保留下來的 0xD800-0xDFFF 區段的碼位來對輔助平面的字元的碼位進行編碼。

### 從 U+0000 至 U+D7FF 以及從 U+E000 至 U+FFFF 的碼位

第一個 Unicode 平面（碼位從 U+0000 至 U+FFFF）包含了最常用的字元。該平面被稱為基本多語言平面，縮寫為 _BMP_（Basic Multilingual Plane，BMP）。UTF-16 與 UCS-2 編碼這個範圍內的碼位為 16 位元長的單個碼元，數值等價於對應的碼位。BMP 中的這些碼位是僅有的可以在 UCS-2 中表示的碼位。

### 從 U+10000 到 U+10FFFF 的碼位

輔助平面（Supplementary Planes）中的碼位，在 UTF-16 中被編碼為**一對** 16 位元長的碼元（即 32 位元，4 位元組），稱作_代理對_（Surrogate Pair），具體方法是：

1. 碼位減去 `0x10000`，得到的值的範圍為 20 位元長的 `0...0xFFFFF`。
2. 高位的 10 位元的值（值的範圍為 `0...0x3FF`）被加上 `0xD800` 得到第一個碼元或稱作高位代理（high surrogate），值的範圍是 `0xD800...0xDBFF`。由於高位代理比低位代理的值要小，所以為了避免混淆使用，Unicode 標準現在稱高位代理為**前導代理**（lead surrogates）。
3. 低位的 10 位元的值（值的範圍也是 `0...0x3FF`）被加上 `0xDC00` 得到第二個碼元或稱作低位代理（low surrogate），現在值的範圍是 `0xDC00...0xDFFF`。由於低位代理比高位代理的值要大，所以為了避免混淆使用，Unicode 標準現在稱低位代理為**後尾代理**（trail surrogates）。

上述演算法可理解為：輔助平面中的碼位從 U+10000 到 U+10FFFF，共計 FFFFF 個，即 220=1,048,576 個，需要 20 位來表示。如果用兩個 16 位元長的整陣列成的序列來表示，第一個整數（稱為前導代理）要容納上述 20 位的前 10 位，第二個整數（稱為後尾代理）容納上述 20 位的後 10 位。還要能根據 16 位元整數的值直接判明屬於前導整數代理的值的範圍（210=1024)，還是後尾整數代理的值的範圍（也是 210=1024）。因此，需要在基本多語言平面中保留不對應於 Unicode 字元的 2048 個碼位，就足以容納前導代理與後尾代理所需要的編碼空間。這對於基本多語言平面總計 65536 個碼位來說，僅占 3.125%。

由於前導代理、後尾代理、BMP 中的有效字元的碼位，三者互不重疊，搜尋是簡單的：一個字元編碼的一部分不可能與另一個字元編碼的不同部分相重疊。這意味著 UTF-16 是自同步（self-synchronizing）的：可以通過僅檢查一個碼元來判定給定字元的下一個字元的起始碼元。UTF-8 也有類似優點，但許多早期的編碼模式就不是這樣，必須從頭開始分析文字才能確定不同字元的碼元的邊界。

由於最常有的字元都在基本多文種平面中，許多軟體處理代理對的部分往往得不到充分的測試。這導致了一些長期的 bug 與潛在安全漏洞，它們甚至存在於廣為流行且評價頗高的應用軟體中。

### 從 U+D800 到 U+DFFF 的碼位

Unicode 標準規定 U+D800...U+DFFF 的值不對應於任何字元。

但是在使用 UCS-2 的時代，U+D800...U+DFFF 內的值被占用，用於某些字元的對映。但只要不構成代理對，許多 UTF-16 編碼解碼還是能把這些不符合 Unicode 標準的字元對映正確的辨識、轉換成合規的碼元。按照 Unicode 標準，這種碼元序列本來應算作編碼錯誤。

### 範例：

以 U+10437 編碼（𐐷）為例:

1. `0x10437` 減去 `0x10000`，結果為 `0x00437`，二進位為 `0000 0000 0100 0011 0111`
2. 分割它的上 10 位值和下 10 位值（使用二進位）：`0000 0000 01` 和 `00 0011 0111`
3. 添加 `0xD800` 到上值，以形成高位：`0xD800 + 0x0001 = 0xD801`
4. 添加 `0xDC00` 到下值，以形成低位：`0xDC00 + 0x0037 = 0xDC37`
* 下表總結了一起範例的轉換過程，顏色指示碼點位如何分布在所述的 UTF-16 中。由 UTF-16 編碼過程中加入附加位的以黑色顯示。

### 範例：UTF-16 編碼程式

假設要將 U+64321（16 進位）轉成 UTF-16 編碼。因為它超過 U+FFFF，所以他必須編譯成 32 位元（4 個 byte）的格式，如下所示：

```
V = 0x64321
Vx = V - 0x10000
= 0x54321
= 0101 0100 0011 0010 0001

Vh = 01 0101 0000 // Vx的高位部份的10 bits
Vl = 11 0010 0001 // Vx的低位部份的10 bits
w1 = 0xD800 //結果的前16位元初始值
w2 = 0xDC00 //結果的後16位元初始值

w1 = w1 | Vh
= 1101 1000 0000 0000
 |       01 0101 0000
= 1101 1001 0101 0000
= 0xD950

w2 = w2 | Vl
= 1101 1100 0000 0000
 |       11 0010 0001
= 1101 1111 0010 0001
= 0xDF21
```

所以這個字 U+64321 最後正確的 UTF-16 編碼應該是：

```
0xD950 0xDF21
```

而在小尾序中最後的編碼應該是：

```
0x50D9 0x21DF
```

因為這個字超過 U+FFFF 所以無法用 UCS-2 的格式編碼。

UTF-16 比起 UTF-8，好處在於大部分字元都以固定長度的位元組（2 位元組）儲存，但 UTF-16 卻無法相容於 ASCII 編碼。

## UTF-16 的編碼模式

UTF-16 的大尾序和小尾序儲存形式都在用。一般來說，以 Macintosh 製作或儲存的文字使用大尾序格式，以 Microsoft 或 Linux 製作或儲存的文字使用小尾序格式。

為了弄清楚 UTF-16 檔案的大小尾序，在 UTF-16 檔案的開首，都會放置一個 U+FEFF 字元作為 Byte Order Mark（UTF-16 LE 以 `FF FE` 代表，UTF-16 BE 以 `FE FF` 代表），以顯示這個文字檔案是以 UTF-16 編碼，其中 U+FEFF 字元在 UNICODE 中代表的意義是 `ZERO WIDTH NO-BREAK SPACE`，顧名思義，它是個沒有寬度也沒有斷字的空白。

以下的例子有四個字元：「朱」（U+6731）、半形逗號（U+002C）、「聿」（U+807F）、「𪚥」（U+2A6A5）。

## UTF-16 與 UCS-2 的關係

UTF-16 可看成是 UCS-2 的父集。在沒有輔助平面字元（surrogate code points）前，UTF-16 與 UCS-2 所指的是同一的意思。但當引入輔助平面字元後，就稱為 UTF-16 了。現在若有軟體聲稱自己支援 UCS-2 編碼，那其實是暗指它不能支援在 UTF-16 中超過 2 位元組的字集。對於小於 0x10000 的 UCS 碼，UTF-16 編碼就等於 UCS 碼。

### Microsoft Windows 作業系統核心對 Unicode 的支援

Windows 作業系統核心中的字元表示為 UTF-16 小尾序，可以正確處理、顯示以 4 位元組儲存的字元。但是 Windows API 實際上僅能正確處理 UCS-2 字元，即僅以 2 位元組儲存的，碼位小於 U+FFFF 的 Unicode 字元。其根源是 Microsoft C++ 語言把 `wchar_t` 資料類型定義為 16 位元的 unsigned short，這就與一個 `wchar_t` 型變數對應一個寬字元、可以儲存一個 Unicode 字元的規定相矛盾。相反，Linux 平台的 GCC 編譯器規定一個 `wchar_t` 是 4 位元組長度，可以儲存一個 UTF-32 字元，寧可浪費了很大的儲存空間。下例執行於 Windows 平台的 C++ 程式可說明此點：

Windows 9x 系統的 API 僅支援 ANSI 字元集，只支援部分的 UCS-2 轉換。1996 年發布的 Windows NT 4.0 的 API 支援 UCS-2。Windows 2000 開始，Windows 系統 API 開始支援 UTF-16，並支援 Surrogate Pair；但許多系統控制項比如文字方塊和 label 等還不支援 surrogate pair 表示的字元，會顯示成兩個字元。Windows 7 及更新的系統已經良好地支援了 UTF-16，包括 Surrogate Pair。

Windows API 支援在 UTF-16LE（`wchar_t` 類型）與 UTF-8（頁碼 CP\_UTF8）之間的轉碼。例如：

## 參考文獻

## 外部連結

* Unicode Technical Note #12: UTF-16 for Processing （頁面存檔備份，存於網際網路檔案館）
* A very short algorithm for determining the surrogate pair for any codepoint（頁面存檔備份，存於網際網路檔案館）
* Unicode FAQ: What is the difference between UCS-2 and UTF-16? （頁面存檔備份，存於網際網路檔案館）
* Unicode Character Name Index （頁面存檔備份，存於網際網路檔案館）
* RFC 2781: UTF-16, an encoding of ISO 10646
* java.lang.String documentation, discussing surrogate handling （頁面存檔備份，存於網際網路檔案館）