UTF-16

UTF-16是Unicode的一種可變長度的字元編碼形式。

它原來是最早期Unicode 1.0所想像,能用16位元的固定長去處理全世界所有文字的UCS-2。但自從Unicode 2.0新增補充平面後,16位元已經不足以表示Unicode內所有文字。所以UTF-16又設計了「代理對」的機制,以兩個16位元的組合去表達補充平面的文字。這讓UTF-16實質上也是一種可變長度的編碼方式。

編碼方式

基本多語言平面內的文字,Unicode碼位本身的值就是正確的UTF-16。意即 U+8140 的 UTF-16 值就是 0x8140。

代理對(surrogate pairs)

Unicode 2.0決定擴充原來的基本多語言平面,新增補充平面後,同時決定將 U+D800 到 U+DFFF 的碼位空下來,專作為UTF-16的代理字元使用。

凡是補充平面(即,非基本多語言平面的其他所有平面)的文字,就必須進行轉換,改以兩個代理字元組成。

代理對計算方式

  1. U+1F63C😼 為例:
    • 0x1F63C 減去 0x10000,結果為 0x0F63C,二進位為 0000 1111 0110 0011 1100 共20位。
    • 將他的二進位前方10位數 00 0011 1101 (0x03D) 加上 0xD800 形成高位 0xD83D。
    • 將他的二進位後方10位數 10 0011 1100 (0x23C) 加上 0xDC00 形成低位 0xDE3C。
  2. U+2C9B0𬦰 為例
    • 0x2C9B0 減去 0x10000,結果為 0x1C9B0,二進位為 0001 1100 1001 1011 0000 共20位。
    • 將他的二進位前方10位數 00 0111 0010 (0x072) 加上 0xD800 形成高位 0xD872。
    • 將他的二進位後方10位數 01 1011 0000 (0x1B0) 加上 0xDC00 形成低位 0xDDB0。

編碼序列

因為遙遠的歷史因素,各機器、作業系統處理16位元序列的順序都不一致。例如Windows與Linux習慣使用 Little Endian(低位在前);而TCP/IP、Java虛擬機器等習慣使用 Big Endian(高位在前)。

由於UTF-16是16位元編碼方式,也受到位元序列的影響,有UTF-16BE、UTF-16LE兩種序列形式。

碼位UTF-16UTF-16BEUTF-16LE
U+0041A004100 4141 00
U+20AC20AC20 ACAC 20
U+2C9B0𬦰D872 DDB0D8 72 DD B072 D8 B0 DD

請注意UTF-16BE與UTF-16LE的差異與代理對無關,而是每一個16位元序列之內的順序問題。

BOM

因為處理UTF-16兩種編碼序列時如果搞錯,會得到完全不同的結果,所以UTF-16純文字文件習慣在前面加上 U+FEFF 字元作為 BOM(Byte Order Mark;位元組順序標註)。在UTF-16BE序列上會是 FE FF 的序列,UTF-16LE序列上則是 FF FE 的序列,以便檢查文件的序列方式。

U+FEFF 字元本身的定義是不占空間也不換行的空格,所以理論上忽略BOM的處理,仍然不會影響到文件內容的正常解讀與處理。另外,依照UTF-8的規格,UTF-8序列中不會出現 0xFF 與 0xFE 的位元組,所以正好用來標示這是UTF-16文件。

使用環境

相較於UTF-8,雖然UTF-16的漢字只佔2 bytes,但所有ASCII字元也都佔用2 bytes的關係,所以平均而言會更佔空間。但因為UTF-16原先是固定長度的,對程式處理而言,16位元固定長度雖然會浪費一些儲存空間,但能帶來計算快速之類的好處(例如字串的切割、尋找第N個字,可以直接存取到正確位置,不需要從序列前方尋找),但隨著補充平面與代理對的出現,此優點已經消失。

故UTF-16主要使用在1996年之前(Unicode 1.0時代還沒有補充平面的時代)第一批支援Unicode的環境,例如Windows系統API、Java虛擬機器、Python 2、JavaScript等。這些實作很多到現在仍然將代理對視為兩個字處理,造成程式處理Emoji、擴充漢字時的困擾。

參考