深入了解 Unicode 文字與符號的編解碼
(圖片取自維基百科)
1987 年 Unicode 誕生至今已經高達近八成的普及率(若包含 ASCII)。跟文字有關的都得處理編解碼問題,設計 Unicode 的野心就是要能表示含全世界的文字符號。(如:Chinese (中文) 到 Russian (русский) 以及 Arabic (العربية) 與其他表情符號)。
在 Unicode 誕生之前的世界
電腦是在美國被發明的,自然而然最初只需要簡單的符號、數字、以及二十六個英文字母,因此最初被普遍使用的字符集就是 ASCII。而 7-Bit 的 ASCII 隨著電腦的普及逐漸發現有些符號與文字無法表示後,加上電腦並不需要有第八位元的錯誤檢查碼,所以 IBM 將其擴展為 8-Bit 的 EBCDIC。
但這始終停留在英美國家,東方世界所使用的漢字系統 CJK(Chinese, Japanese, Korean) 完全只能使用自己定義的字符集,而歐洲國家也同樣如此。每個國家的語言文字都對應到一個以上的編解碼方式,這對寫程式的人是非常痛苦的。試想,如果一篇文章中參雜中文與拉丁文,是無法用同一種編碼方式處理的,
所以在 Unicode 出現之前,不可能寫多語言的文章。瀏覽器也會非常難實作,可能中文網頁跳轉到別的國家網站,就變成亂碼了。為了解決這個問題,微軟與蘋果當初最新跳出來提倡了 ISO-2022 的標準,符合這規範標準的字符集互相可以透過 shift sequeences 結合使用,但直到 Unicode 的出現,電腦上的語言隔閡才真正的被消弭。
Unicode 的編碼
大多人都會以為 Unicode 是種編碼,其實不然,Unicode 代表著文字符號的集合,每個文字符號當然都會依序有對應的編號,依重要性高至低,給予編號。
為什麼需要編碼?
那麼既然有了編號,不也是一種編碼嗎?為什麼還需要額外的編碼?理論上來說,我們的確是不需要 UTF (Unicode Transformation Format) 或是 UCS(Universal Character Set)這類的 Codec。不過因為 Unicode 無所不在(如:輸入法、郵件、網路、作業系統、字型),編解碼的速度以及空間使用的效率就非常重要了!
也因為講究效能,在 Unicode 誕生後,不同的機器上大家採用的最佳化方式也不同(考慮 Little/Big Endian),造成編碼方式的混亂。可知 Unicode 本身就算有基本的編號,也因大家的實作不同,必須要明確指出使用的編碼方式(即使你不想要最佳化)。
UTF 編碼中的數字代表 Bit,UCS 中的數字代表 Byte。
檔案中存放的二進位碼為 0000000: e593 88e5 9b89 0a .......
>>> h.decode('utf8')
u'\u54c8\u56c9'
當我們提到 Unicode 編碼時,通常指的會是以下幾種編碼方式:
UTF-8
- 最普遍被使用的編碼方式之一(請參考經典的UTF-8)
- 可變長度,大部分字符可以 3-Byte 表示
- 相容 ASCII
- Endian-Neutral,在不同機器與 OS 間不需要特別處理
- 外部編碼需要訊息交換的首選
UTF-16
- 最普遍被使用的編碼方式之一
- 可變長度,大部分字符可以 2-Byte 表示
- 和 ASCII 和 ISO-8859-1 不相容
- 需要考慮 Little/Big Endian,所以需要 BOM 的存在
- 空間使用效率高,被被廣泛用於程式的內部編碼,如 Java。
UTF-32:同 UCS-4
- 固定長度
- 簡單,不需要處理字符定位問題
- 需要考慮 Little/Big Endian,所以需要 BOM 的存在
- 浪費記憶體空間
UCS-4:功能上與 UTF-32 相同
UCS-2:已過時的 UTF-16 子集
常見名詞解釋
- Code Page: 含義同於字符編碼集(Character Encoding),Windows 中 UTF-8 對應的 Code Page 為 65001,Big5 為 950。
- Code Space: 該編碼所有會用到的數字範圍,Unicode UTF-8 為 1,114,112。
- Code Point:與字元間有一對一的關係,例如:「哈」對應到 UTF-8 編碼為 ,而'\xe5\x93\x88'
舉例來說:
- With US-ASCII, code unit is 7 bits.
- With UTF-8, code unit is 8 bits.
- With EBCDIC, code unit is 8 bits.
- With UTF-16, code unit is 16 bits.
- With UTF-32, code unit is 32 bits.
Unicode 編解碼無所不在
因為編碼到處都會用到,光是在網頁輸入文字,就會牽扯到「輸入法->作業系統->瀏覽器->網頁」間的編解碼轉換,因此得特別留意設定是否正確才可以避免編解碼錯誤產生的亂碼。(不知道程式採用何種編碼時,可以試試用 Universal Encoding Detector 來檢查。)
VIM 編輯器
將 VIM、終端機、以及檔案使用的編碼方式都改為 UTF-8:
Python 可以透過直譯器或是 Script 兩種方式執行,如果透過直譯器執行的話,會被終端機與 Shell 的編碼方式影響,因此我們需要設定以下環境變數:
Windows
因為 Windows 在 Unicode 誕生前就存在,因此處理 Unicode 相對來說沒有這麼直覺。
- MBCS (Multi Byte Char System) 指的是繁體中文(BIG5),簡體中文(GBK)這類的字符集。
- WideChar 指的是 Unicode。
- 這些字元的編碼與 Unicode 轉換可以使用 MultiByteToWideChar/WideCharToMultiByte。
- _stprintf 就是 Unicode 的 sprintf,因為 sprintf 就只是給 MBCS 使用。
- LPSTR, pointer to 32 bits string
- LPWSTR, pointer to 32 bits unicode string
- LPCWSTR, pointer to 32 bits unicode constant string
- 要在 C 語言中使用 Unicode 字串,要使用 wchar_t 取代 char,然後用對應的函數取代原本的字串操作函數。
- 沒特別說明,Unicode 在 Windows 指的是 UTF-16 Little Endian。