2012年4月25日 星期三

備忘: Unicode, UTF-8, CESU-8

最近都在跟 Redis 打交道, 我們的專案內部的字串資料都是用 wchar_t 的型式來操作的, 沒想到一考慮到跨平台就要面臨 Windows 和 Unix 對於 wchar_t 長度各自表述的問題. 又為了省空間 (字串純英數的比例較大), 在從資料庫存取字串時, 都加上了 UTF-8 (正確來說, 是 CESU-8) 轉換. 費了很多意料之外的時間在處理這些相關問題. 簡單整理了幾點注意事項備忘:
  • Unicode 定義中, 一個字碼單位稱為一個 code point. 每 65,535 個 code points 組成一個 plane. 而目前總共定義有 17 個 planes. 最常用的字碼都集中在第一個 plane 中 (plane #0), 該 plane 又稱為 BMP (Basic Multilingual Plane).
  • Windows 中的 wchar_t 長度是 16 bits, 使用 UTF-16 編碼. 16 bits 只有辦法涵蓋一個 plane 的字碼量, 因此正常來說就是映射到 BMP 的字碼. 罕見情況下需要使用其它 plane 中的字碼時, 就用連續的兩個 wchar_t 來表示一個 Unicode code point (稱為 Surrogate Pair, 代理對). 所以不要認為 Windows 系統上所有的 wchar_t 字串  raw bytes 長度除以 2 就是字串長. 有機會陰溝裡翻船.
  • Unix 中的 wchar_t 長度是 32 bits, 使用 UTF-32 編碼. 因為 32 bits 已經可以涵蓋所有 planes 所有 code points. 所以 Unix 上一個 wchar_t 一定就對應到一個 Unicode code point.
  • UTF-8 轉換理論上要能函蓋所有 code points, 但實際上大多數的 UTF-8 轉換實作都只針對 BMP 中的 code points. 意即每次讀入一個 UTF-16 2bytes 轉換為 UTF-8 1~3bytes, 且不考慮 Surrogate Pair, 因此 Surrogate Pair 的兩個 wchar_t 會被錯誤地當成兩個獨立的 UTF-16 字碼進行轉換. 現今漸漸改用 CESU-8 這個詞來稱呼這種較簡略且非正規的轉換法. CESU 為 Compatibility Encoding Scheme for UTF-16 的縮寫.
  • Modified UTF-8 原理其實跟 CESU-8 是幾乎相同的 (所以正確來說, 應該叫 Modified CESU-8), 只差在 UTF-16 中的 NULL (即 0x0000) 並不轉換為 (0x00), 而是 (0xC080) 的雙字節, 以避免該 (0x00) 被當成字串結尾.
  • 包含 Orcale, Java, Dalvik, Tcl, 等等重量級的軟體專案, 其中號稱的 UTF-8 轉換, 其實都只有達到 CESU-8 或 Modified UTF-8 的程度.


References:
  1. http://en.wikipedia.org/wiki/Unicode
  2. http://en.wikipedia.org/wiki/UTF-8
  3. http://en.wikipedia.org/wiki/CESU-8

沒有留言:

張貼留言