作為一個計算機的多字節字符表示系統,Unicode 支持世界上所有語言的編碼和轉換。這篇文章說明了 Linux 應用程序中的國際語言支持的重要性,以及設計 Unicode 支持並將之結合到 Linux 應用程序中去的思想。 Unicode 並不只是一個編程工具,它還是一個政治的、經濟的工具。沒有結合世界的語言支持的應用程序通常只能被那些能讀寫 ASCII 所支持語言的個人使用。這使得建立在 ASCII 基礎之上的計算機技術脫離了世界上大部分人。Unicode 允許程序使用世界上任何一種字符集,因此它支持所有語言。 Unicode 讓程序員為普通人提供用他們本國語言就能使用的軟件。這樣就不用再學一門外語了,而且更容易實現計算機技術社會和財政上的利益。很容易設想,如果用戶必須為使用因特網浏覽器而學習烏爾都語的話,您就難以看到計算機在美國的使用。Web 就更不會出現了。 Linux 承擔了對 Unicode 很大程度上的支持。Unicode 支持被嵌入到內核和代碼開發庫中。在很大程度上,使用程序中幾句簡單的命令就能將它們自動的結合到代碼中。 所有現代字符集的基礎都是在 1968 年以 ANSIX3.4 版本出版的美國信息交換標准碼(American Standard Code for Information Interchange,ASCII)。一個值得注意的例外是在 ASCII 之前定義的 IBM 的擴充的二進制編碼的十進制交換碼(Extended Binary Coded Decimal Information Code,EBCDIC)。ASCII 是一個編碼字符集(coded character set,CCS),換句話說,它是整數到字符表示的映射。ASCII 編碼字符集允許用一個八位(基於二進制的,用值 0 或 1 表示的)字段或字節(2^8 =256)表示 256 個字符。這是一個高度受限的編碼字符集,它不能表示許多不同語言的所有字符(如中文和日文),不能表示科學符號,更不能表示古代文字(神秘符號和象形文字)和音樂符號。通過更改一個字節的長度而使更大的字符集得以被編碼,這似乎有效但完全不切實際。所有的計算機都基於八位字節。解決方法是一種字符編碼方案(Character encoding scheme,CES)— 用定長或變長的多字節序列能夠表示比 256 大的數.這些數值接著通過編碼字符集被映射到它們表示的字符。 Unicode 的定義 Unicode 通常用作涉及雙字節字符編碼方案的通用術語。Unicode CCS 3.1 的官方稱謂是 ISO10646-1 通用多八字節編碼字符集(Universal Multiple Octet Coded Character Set,UCS)。Unicode 3.1 版本添加了 44,946 個新的編碼字符。算上 Unicode 3.0 版本已經存在的 49,194 個字符,共計 94,140 個。 Unicode 編碼字符集利用了一個由 128 個三維的組構成的四維編碼空間。其中每個組包含 256 個二維平面。每個平面由 256 個一維的行組成,並且每個行有 256 個單元。每個單元在這個編碼空間內對一個字符編碼,或者被聲明為未經使用。這種編碼概念被稱為 UCS-4;四個八位元用來表示指定組、平面、行和單元的每個字符。 第一個平面(第 00 組的第 00 平面)是基本多語言平面(Basic Multilingual Plane,BMP)。BMP 按字母、音節、表意符號和各種符號及數字定義了常規使用的字符。後續的平面用於附加字符或其它還沒有發明的編碼實體。我們需要這完整的范圍去處理世界上的所有語言;特別是擁有將近 64,000 個字符的一些東亞語言。 BMP 被用作雙字節的編碼字符集,這種編碼字符集確定為 ISO 10646 UCS-2 格式。ISO 10646 UCS-2 就是指 Unicode(並且兩者相同)。BMP,像所有 UCS 平面那樣,包含了 256 行,其中每行包含 256 個單元,字符僅僅按照 BMP 中的行和單元的八位元在單元中被編碼。 這就允許 16 位編碼字符能夠被用來書寫大多數商業上最重要的語言。UCS-2 不需要代碼頁切換、代碼擴展或代碼狀態。UCS-2 是一種將 Unicode 結合到軟件中的簡單方法,但它只限於支持 Unicode BMP。 若要用 8 位字節表示一個多於 2^8 =256 個字符的字符編碼系統(character coding system,CCS),就需要一種字符編碼方案(character-encoding scheme,CES)。 Unicode 轉換 在 UNIX 中,使用得最多的字符編碼方案是 UTF-8。 它考慮到了對整個 Unicode 全部頁和平面的全面支持,而且它仍能正確的識別 ASCII。除了 UTF-8 的其他選擇還有:UCS-4、UTF-16、UTF-7.5、UTF-7、SCSU、Html 和 Java。 Unicode 轉換格式(Unicode Transformation Formats,UTFs)是一種通過映射多字節編碼中的值來支持 Unicode 的字符編碼方案。本文將分析最流行的格式 — UTF-8 字符編碼系統。 UTF-8 UTF-8 轉換格式正逐步成為一種占主導地位的交換國際文本信息的方法,因為它可以支持世界上所有的語言,而且它還與 ASCII 兼容。UTF-8 使用變長編碼。從 0 到 0x7f(127)的字符把自身編碼成單字節,而將值更大的字符編碼成 2 到 6 個字節。 表 1. UTF-8 編碼 0x00000000 - 0x0000007F: 0xxxxxxx 0x00000080 - 0x000007FF: 110xxxxx 10xxxxxx 0x00000800 - 0x0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx 0x00010000 - 0x001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 0x00200000 - 0x03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 0x04000000 - 0x7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 字節 10xxxxxx 是一個擴展字節,它的 xxxxxx 位位置被以二進制表示的字符代碼號的位所填充。這是能夠代表被使用代碼的最短的可能的多字節序列。 UTF-8 編碼示例 Unicode 字符版權標記字符 0xA9 = 1010 1001 用 UTF-8 編碼如下所示: 11000010 10101001 = 0xC2 0xA9 “不等於”符號字符 0x2260 = 0010 0010 0110 0000 編碼如下所示: 11100010 10001001 10100000 = 0xE2 0x89 0xA0 通過獲取 continuation byte 的值可以看到原始數據: [1110]0010 [10]001001 [10]100000 0010 001001 100000 0010 0010 0110 0000 = 0x2260 第一個字節定義後面緊跟的八位元數,如果是 7F 或更小,這就是等價的 ASCII 值。每個八位字節以 10xxxxxx 開頭,確保字節不與 ASCII 的值混淆。 UTF 支持 在 Linux 平台上使用 UTF-8 之前,請確信分發包裡有 glibc 2.2 和 XFree86 4.0 或更新的版本。早先的版本缺少 UTF-8 語言環境支持和 ISO10646-1 X11 字體。 在 UTF-8 發布之前,Linux 用戶使用各種不同特定語言的擴展 ASCII,像歐洲用戶用 ISO 8859-1 或 ISO 8859-2,希臘用戶使用 ISO 8859-7,俄羅斯用戶使用 KOI-8 / ISO 8859-5/CP1251(西裡爾字母)。這使得數據交換出現了很多問題,並且需要為這些編碼之間的差異編寫應用軟件。這種語言支持是不完善的,而且數據交換沒有經過測試。Linux 主要的發行商和應用程序開發者正致力於讓主要以 UTF-8 格式表示的 Unicode 成為 Linux 中的標准。 為了識別 Unicode 文件,Microsoft 建議所有的 Unicode 文件應該以 ZERO WIDTH NOBREAK SPACE(U+FEFF)字符開頭。這作為一個“特征符”或“字節順序標記(byte-order mark,BOM)”來識別文件中使用的編碼和字節順序。但是,Linux/UNIX 並沒有使用 BOM,因為它會破壞現有的 ASCII 文件的語法約定。在 POSIX 系統中,選中的語言環境識別了在一個過程中的所有輸入輸出文件期望的編碼形式。 有兩種方法可以將 UTF-8 支持添加到 Linux 應用程序中。第一種方法,數據都以 UTF-8 形式存放在各處,這樣軟件改動很少(被動的)。另一種方法,被讀取的 UTF-8 數據用標准的 C 語言庫函數轉變成為寬字符數組(轉換的)。在輸出時,用函數 wcsrtombs() 使字符串被轉變回 UTF-8: 清單 1. wcsrtombs() #include size_t wcsrtombs (char *dest, const wchar_t **src, size_t len, mbstate_t *ps); 方法的選擇取決於應用程序的性質。大多數應用程序可以使用被動的方法操作。這就是在 UNIX 平台上使用 UTF-8 會如此流行的原因。像 cat 和 echo 那樣的程序就不需要修改。字節流仍只是字節流,並沒有對它進行任何處理。ASCII 字符和控制代碼在 UTF-8 語言環境中不改變。 通過字節計數對字符進行計數的程序需要一些小小的改動。在 UTF-8 中應用程序不對任何擴展的字節進行計數。如果選擇了 UTF-8 語言環境,C 語言庫的 strlen(s) 函數需要用 mbstowcs() 函數來代替: 清單 2. mbstowcs() 函數 #include size_t mbstowcs(wchar_t *pwcs, const char *s, size_t n); strlen 的一種常見用法是估算顯示寬度。中文和其它表意符號將占用兩列位置。 wcwidth() 函數用來測試每個字符的顯示寬度: 清單 3. wcwidth() 函數 #include int wcwidth(wchar_t wc); Unicode 的 C 語言支持 在正式情況下,從 GNU glibc 2.2 開始,wchar_t 類型只為 32 位的 ISO 10646 格式數值所特定使用,與當前使用的語言環境無關。通過 ISO C99 所要求的 __STDC_ISO_10646__ 宏的定義作為信號通知應用程序。 __STDC_ISO_10646__ 的定義用來指出 wchar_t 是 Unicode。精確的值是一個十進制的 yyyymmL 格式的常數。例如,使用: 清單 4. 指出 wchar_t 是 Unicode #define __STDC_ISO_10646__ 200104L 是為指出 wchar_t 類型的值是由 ISO/IEC 10646 和到指定的年月為止的所有修正與技術勘誤定義的字符編碼表示。 對 wchar_t 的利用如這個示例