UTF16 是 Unicode 字元編碼五層次模型的第三層：字元編碼表（Character Encoding Form，也稱為 storage format）的一種實現方式。即把 Unicode 字元集的抽象碼位對映為 16 位長的整數（即碼元）的 序列，用於資料儲存或傳遞。Unicode 字元的碼位，需要 1 個或者 2 個 16 位長的碼元來表示，因此這是一個變長表示。 UTF 是 UnicodeUCS Transformation Format 的首字母縮寫，即把 Unicode 字元轉換為某種格式之意。UTF16 正式定義於 ISOIEC 106461 的附錄 C，而 RFC2781 也定義了相似的做法。 UTF16 描述 Unicode 的編碼空間從 U0000 到 U10FFFF，共有 1112064 個碼位（code point）可用來對映字元。Unicode 的編碼空間可以劃分為 17 個平面（plane），每個平面包含 216（65536）個碼位。17 個平面的碼位可表示為從 Uxx0000 到 UxxFFFF，其中 xx 表示十六進位制值從 0016 到 1016，共計 17 個平面。第一個平面稱為基本多語言平面（Basic Multilingual Plane BMP），或稱第零平面（Plane 0），其他平面稱為輔助平面（Supplementary Planes）。基本多語言平面內，從 UD800 到 UDFFF 之間的碼位區段是永久保留不對映到 Unicode 字元。UTF16 就利用保留下來的 0xD8000xDFFF 區段的碼位來對輔助平面的字元的碼位進行編碼。 從 U0000 至 UD7FF 以及從 UE000 至 UFFFF 的碼位 第一個 Unicode 平面（碼位從 U0000 至 UFFFF）包含了最常用的字元。該平面被稱為基本多語言平面，縮寫為 BMP（Basic Multilingual Plane，BMP）。UTF16 與 UCS2 編碼這個範圍內的碼位為 16 位元長的單個碼元，數值等價於對應的碼位。BMP 中的這些碼位是僅有的可以在 UCS2 中表示的碼位。 從 U10000 到 U10FFFF 的碼位 輔助平面（Supplementary Planes）中的碼位，在 UTF16 中被編碼為一對 16 位元長的碼元（即 32 位元，4 位元組），稱作代理對（Surrogate Pair），具體方法是： UTF16 解碼 lead trail DC00 DC01 DFFF D800 10000 lead trail DC00 DC01 DFFF D800 10000 10001 103FF D801 10400 10401 107FF DBFF 10FC00 10FC01 10FFFF 碼位減去 0x10000，得到的值的範圍為 20 位元長的 00xFFFFF。 高位的 10 位元的值（值的範圍為 00x3FF）被加上 0xD800 得到第一個碼元或稱作高位代理（high surrogate），值的範圍是 0xD8000xDBFF。由於高位代理比低位代理的值要小，所以為了避免混淆使用，Unicode 標準現在稱高位代理為前導代理（lead surrogates）。 低位的 10 位元的值（值的範圍也是 00x3FF）被加上 0xDC00 得到第二個碼元或稱作低位代理（low surrogate），現在值的範圍是 0xDC000xDFFF。由於低位代理比高位代理的值要大，所以為了避免混淆使用，Unicode 標準現在稱低位代理為後尾代理（trail surrogates）。 上述演算法可理解為：輔助平面中的碼位從 U10000 到 U10FFFF，共計 FFFFF 個，即 2201048576 個，需要 20 位來表示。如果用兩個 16 位長的整陣列成的 序列 來表示，第一個整數（稱為前導代理）要容納上述 20 位的前 10 位，第二個整數（稱為後尾代理）容納上述 20 位的後 10 位。還要能根據 16 位整數的值直接判明屬於前導整數代理的值的範圍（2101024，還是後尾整數代理的值的範圍（也是 2101024）。因此，需要在基本多語言平面中保留不對應於 Unicode 字元的 2048 個碼位，就足以容納前導代理與後尾代理所需要的編碼空間。這對於基本多語言平面總計 65536 個碼位來說，僅佔 3125。 由於前導代理、後尾代理、BMP 中的有效字元的碼位，三者互不重疊，搜尋是簡單的：一個字元編碼的一部分不可能與另一個字元編碼的不同部分相重疊。這意味著 UTF16 是自同步（selfsynchronizing）的：可以透過僅檢查一個碼元來判定給定字元的下一個字元的起始碼元。UTF8 也有類似優點，但許多早期的編碼模式就不是這樣，必須從頭開始分析文字才能確定不同字元的碼元的邊界。 由於最常有的字元都在基本多文種平面中，許多軟體處理代理對的部分往往得不到充分的測試。這導致了一些長期的 bug 與潛在安全漏洞，它們甚至存在於廣為流行且評價頗高的應用軟體中。 從 UD800 到 UDFFF 的碼位 Unicode 標準規定 UD800UDFFF 的值不對應於任何字元。 但是在使用 UCS2 的時代，UD800UDFFF 內的值被佔用，用於某些字元的對映。但只要不構成代理對，許多 UTF16 編碼解碼還是能把這些不符合 Unicode 標準的字元對映正確的辨識、轉換成合規的碼元 Python 26 decode of UTF16 does this on Linux and it correctly handles surrogate pairs All CESU decoders do it too though they also mistranslate correct surrogate pairs into 2 characters。按照 Unicode 標準，這種碼元序列本來應算作編碼錯誤。 範例： 以 U10437 編碼（𐐷）為例 0x10437 減去 0x10000，結果為 0x00437，二進位制為 分割它的上 10 位值和下 10 位值（使用二進位制）： 和 新增 0xD800 到上值，以形成：0xD800 0x0001 新增 0xDC00 到下值，以形成：0xDC00 0x0037 下表總結了一起示例的轉換過程，顏色指示碼點位如何分佈在所述的 UTF16 中。由 UTF16 編碼過程中加入附加位的以黑色顯示。 字元 字元 普通二進位制 UTF16 二進位制 UTF16 十六進位制字元程式碼 UTF16BE 十六進位制位元組 UTF16LE 十六進位制位元組 U0024 U20AC 𐐷 U10437 1101 10 1101 11 𤭢 U24B62 1101 10 1101 11 範例：UTF16 編碼程式 假設要將 U64321（16 進位）轉成 UTF16 編碼。因為它超過 UFFFF，所以他必須編譯成 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 所以這個字 U64321 最後正確的 UTF16 編碼應該是： 0xD950 0xDF21 而在小尾序中最後的編碼應該是： 0x50D9 0x21DF 因為這個字超過 UFFFF 所以無法用 UCS2 的格式編碼。 16 進位制編碼範圍 UTF16 表示方法（二進位制） 10 進位制碼範圍 位元組數量 U0000 UFFFF xxxx xxxx xxxx xxxx yyyy yyyy yyyy yyyy 065535 2 U10000 U10FFFF 1101 10yy yyyy yyyy 1101 11xx xxxx xxxx 655361114111 4 UTF16 比起 UTF8，好處在於大部分字元都以固定長度的位元組（2 位元組）儲存，但 UTF16 卻無法相容於 ASCII 編碼。 UTF16 的編碼模式 UTF16 的大尾序和小尾序儲存形式都在用。一般來說，以 Macintosh 製作或儲存的文字使用大尾序格式，以 Microsoft 或 Linux 製作或儲存的文字使用小尾序格式。 為了弄清楚 UTF16 檔案的大小尾序，在 UTF16 檔案的開首，都會放置一個 UFEFF 字元作為 Byte Order Mark（UTF16 LE 以 FF FE 代表，UTF16 BE 以 FE FF 代表），以顯示這個文字檔案是以 UTF16 編碼，其中 UFEFF 字元在 UNICODE 中代表的意義是 ZERO WIDTH NOBREAK SPACE，顧名思義，它是個沒有寬度也沒有斷字的空白。 以下的例子有四個字元：「朱」（U6731）、半形逗號（U002C）、「聿」（U807F）、「」（U2A6A5）。 使用 UTF16 編碼的例子 使用 UTF16 編碼的例子 使用 UTF16 編碼的例子 使用 UTF16 編碼的例子 使用 UTF16 編碼的例子 使用 UTF16 編碼的例子 使用 UTF16 編碼的例子 編碼名稱 編碼次序 編碼 編碼 編碼 編碼 編碼 編碼名稱 編碼次序 BOM 朱 聿 UTF16 LE 小尾序，不含 BOM 31 67 2C 00 7F 80 69 D8 A5 DE UTF16 BE 大尾序，不含 BOM 67 31 00 2C 80 7F D8 69 DE A5 UTF16 LE 小尾序，包含 BOM FF FE 31 67 2C 00 7F 80 69 D8 A5 DE UTF16 BE 大尾序，包含 BOM FE FF 67 31 00 2C 80 7F D8 69 DE A5 UTF16 與 UCS2 的關係 UTF16 可看成是 UCS2 的父集。在沒有輔助平面字元（surrogate code points）前，UTF16 與 UCS2 所指的是同一的意思。但當引入輔助平面字元後，就稱為 UTF16 了。現在若有軟體聲稱自己支援 UCS2 編碼，那其實是暗指它不能支援在 UTF16 中超過 2 位元組的字集。對於小於 0x10000 的 UCS 碼，UTF16 編碼就等於 UCS 碼。 Microsoft Windows 作業系統核心對 Unicode 的支援 Windows 作業系統核心中的字元表示為 UTF16 小尾序，可以正確處理、顯示以 4 位元組儲存的字元。但是 Windows API 實際上僅能正確處理 UCS2 字元，即僅以 2 位元組儲存的，碼位小於 UFFFF 的 Unicode 字元。其根源是 Microsoft C 語言把 wchar_t 資料型別定義為 16 位元的 unsigned short，這就與一個 wchar_t 型變數對應一個寬字元、可以儲存一個 Unicode 字元的規定相矛盾。相反，Linux 平臺的 GCC 編譯器規定一個 wchar_t 是 4 位元組長度，可以儲存一個 UTF32 字元，寧可浪費了很大的儲存空間。下例運行於 Windows 平臺的 C 程式可說明此點： 此原始檔在 Windows 平臺上必須儲存為 Unicode 格式（即 UTF16 小尾） 因為包含的漢字 𪚥，不能在簡體中文版 Windows 預設的內碼表 936（即 GBK）中表示 該漢字在 UTF16 小尾序中用 4 個位元組表示 Windows 作業系統能正確顯示這樣的在 UTF16 需用 4 位元組表示的字元 但是 Windows API 不能正確處理這樣的在 UTF16 需用 4 位元組表示的字元，把它判定為 2 個 UCS2 字元 include windowsh include stdioh int main const wchar_t lwc L𪚥 MessageBoxW NULL lwc lwc MB_OK int i wcslen lwc printf dn i int j lstrlenW lwc printf dn j return 0 Windows 9x 系統的 API 僅支援 ANSI 字元集，只支援部分的 UCS2 轉換。1996 年釋出的 Windows NT 40 的 API 支援 UCS2。Windows 2000 開始，Windows 系統 API 開始支援 UTF16，並支援 Surrogate Pair；但許多系統控制元件比如文字框和 label 等還不支援 surrogate pair 表示的字元，會顯示成兩個字元。Windows 7 及更新的系統已經良好地支援了 UTF16，包括 Surrogate Pair。 Windows API 支援在 UTF16LE（wchar_t 型別）與 UTF8（內碼表 CP_UTF8）之間的轉碼。例如： include windowsh 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 UTF16 for Processing A very short algorithm for determining the surrogate pair for any codepoint Unicode FAQ What is the difference between UCS2 and UTF16 Unicode Character Name Index UTF16 an encoding of ISO 10646 javalangString documentation discussing surrogate handling CategoryUnicode 轉換格式