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），具體方法是：

lead \ trail｜DC00｜DC01｜…｜DFFF

D800｜10000｜10001｜…｜103FF

D801｜10400｜10401｜…｜107FF

⋮｜⋮｜⋮｜⋱｜⋮

DBFF｜10FC00｜10FC01｜…｜10FFFF

- 碼位減去 `0x10000`，得到的值的範圍為20比特長的 `0...0xFFFFF`。
- 高位的10比特的值（值的範圍為 `0...0x3FF`）被加上 `0xD800` 得到第一個碼元或稱作高位代理（high surrogate），值的範圍是 `0xD800...0xDBFF`。由於高位代理比低位代理的值要小，所以為了避免混淆使用，Unicode標準現在稱高位代理為前導代理（lead surrogates）。
- 低位的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編碼（𐐷）為例:

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

字符｜普通二進位｜UTF-16二進位｜UTF-16 十六進位字符代碼｜UTF-16BE十六進位字節｜UTF-16LE十六進位字節

$｜`U+0024`｜`#0070FF`｜`#0070FF`｜`#0070FF`｜`#0070FF`｜`#0070FF`

€｜`U+20AC`｜`#0070FF`｜`#0070FF`｜`#0070FF`｜`#0070FF`｜`#0070FF`

𐐷｜`U+10437`｜`#00AA00#0070FF`｜`1101 10#00AA00 1101 11#0070FF`｜`#00AA00 #0070FF`｜`#00AA00 #0070FF`｜`#00AA00 #0070FF`

𤭢｜`U+24B62`｜`#00AA00#0070FF`｜`1101 10#00AA00 1101 11#0070FF`｜`#00AA00 #0070FF`｜`#00AA00 #0070FF`｜`#00AA00 #0070FF`

### 範例：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的格式編碼。

16進制編碼範圍｜UTF-16表示方法（二進制）｜10進制碼範圍｜字節數量

`U+0000` - `U+FFFF`｜`xxxx xxxx xxxx xxxx` - `yyyy yyyy yyyy yyyy`｜0-65535｜2

`U+10000` - `U+10FFFF`｜`1101 10yy yyyy yyyy` - `1101 11xx xxxx xxxx`｜65536-1114111｜4

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編碼的例子

編碼名稱｜編碼次序｜編碼

BOM｜朱｜,｜聿｜𪚥

UTF-16 LE｜小尾序，不含BOM｜31 67｜2C 00｜7F 80｜69 D8 A5 DE

UTF-16 BE｜大尾序，不含BOM｜67 31｜00 2C｜80 7F｜D8 69 DE A5

UTF-16 LE｜小尾序，包含BOM｜FF FE｜31 67｜2C 00｜7F 80｜69 D8 A5 DE

UTF-16 BE｜大尾序，包含BOM｜FE FF｜67 31｜00 2C｜80 7F｜D8 69 DE A5

## 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++程序可說明此點：

```cpp
// 此源文件在Windows平台上必须保存为Unicode格式（即UTF-16小尾）
// 因为包含的汉字“𪚥”，不能在简体中文版Windows默认的代码页936（即GBK）中表示
// 该汉字在UTF-16小尾序中用4个字节表示
// Windows操作系统能正确显示这样的在UTF-16需用4字节表示的字符
// 但是Windows API不能正确处理这样的在UTF-16需用4字节表示的字符，把它判定为2个UCS-2字符

#include <windows.h>
#include <stdio.h>

int main()
{
	const wchar_t lwc[] = L"𪚥";

	MessageBoxW(NULL, lwc, lwc, MB_OK);

	int i = wcslen(lwc);
	printf("%d\n", i);
	int j = lstrlenW(lwc);
	printf("%d\n", j);

	return 0;
}
```

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）之間的轉碼。例如：

```cpp
#include <windows.h>
int main() {
	char a1[128], a2[128] = { "Hello" };
	wchar_t w = L'页';
	int n1, n2= 5;
	wchar_t w1[128];
	int m1 = 0;

	n1 = WideCharToMultiByte(CP_UTF8, 0, &w, 1, a1, 128, NULL, NULL);
	m1 = MultiByteToWideChar(CP_UTF8, 0, a2, n2, w1, 128);
}
```

## 外部連結

- 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
- : UTF-16, an encoding of ISO 10646
- java.lang.String documentation, discussing surrogate handling