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 <wchar.h> 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 <stdlib.h> size_t mbstowcs(wchar_t *pwcs, const char *s, size_t n);
|
strlen 的一種常見用法是估算顯示寬度。中文和其它表意符號將佔用兩列位置。 wcwidth() 函數用來測試每個字元的顯示寬度:
清單 3. wcwidth() 函數
#include <wchar.h> 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 的利用如這個樣本所示,使用宏確定在 ISO C99 可移植代碼中寫雙引號的方法。
清單 5. 確定寫雙引號的方法
#if __STDC_ISO_10646__ printf("%lc", 0x201c); #else putchar('"'); #fi
|
語言環境
啟用
UTF-8 的恰當的辦法是 POSIX
語言環境機制。語言環境是一種包含有關軟體行為特定文化約定的配置設定。它包含了字元編碼、日期/時間符號、分類規則以及度量系統。語言環境的名稱通常由
ISO 639-1 語言、ISO 3166-1 國家或地區代碼以及可選的編碼名稱和其它限定符組成。您可以用命令 locale -a 擷取所有安裝在系統上的語言環境列表(通常在 /usr/lib/locale/)。
如果沒有預先安裝 UTF-8 語言環境,你可以用 localedef 命令產生它。若要為某個特定使用者產生並啟用一個德語的 UTF-8 語言環境,請使用如下語句:
清單 6. 為特定使用者產生語言環境
localedef -v -c -i de_DE -f UTF-8 $HOME/local/locale/de_DE.UTF-8 export LOCPATH=$HOME/local/locale export LANG=de_DE.UTF-8
|
有時候為所有使用者添加 UTF-8 語言環境會很有用。root 使用者使用如下指令就可以完成:
清單 7. 為每個使用者產生語言環境
localedef -v -c -i de_DE -f UTF-8 /usr/share/locale/de_DE.UTF-8
|
若要為每個使用者將這個語言環境設為預設值,可以將以下行添加到 /etc/profile 檔案中:
清單 8. 為所有使用者佈建預設的語言環境
處理多位元組字元代碼序列的函數行為依賴於當前語言環境的 LC_CTYPE
類別;它確定了依賴語言環境的多位元組編碼。值 LANG=de_DE(德語)會導致輸出按 ISO 8859-1 被格式化。值
LANG=de_DE.UTF-8 會把輸出格式化成 UTF-8。語言環境設定會導致 printf 中的 %ls 格式說明符調用 wcsrtombs()
函數以便於將寬字元的參數字串轉換成依賴語言環境的多位元組編碼。語言環境中的國家或地區標識符如:LC_CTYPE= en_GB (英國英語)和
LC_CTYPE= en_AU(澳大利亞英語),它們之間的差異只在 LC_MONETARY 類別中,原因在於貨幣的名稱和列印貨幣數量的規則不同。
請給您首選的語言環境設定環境變數 LANG。當一個 C 程式執行 setlocale() 函數時:
清單 9. setlocale() 函數
#include <stdio.h> #include <locale.h> //char *setlocale(int category, const char *locale); int main() { if (!setlocale(LC_CTYPE, "")) { fprintf(stderr, "Locale not specified. Check LANG, LC_CTYPE, LC_ALL. "); return 1; }
|
C 語言庫將會依次測試環境變數 LC_ALL、LC_CTYPE 和
LANG。其中第一個含值的環境變數將決定為 LC_CTYPE 類別裝入哪種語言環境資料。語言環境資料分裂成獨立的類別。值 LC_CTYPE
定義了字元編碼,而 LC_COLLATE 定義了排序次序。我們用 LANG 環境變數為所有類別設定預設語言環境,但 LC_*
變數可以用來覆蓋單個類別。
您可以用命令 locale charmap 查詢當前語言環境中字元編碼的名稱。如果您從 LC_CTYPE 類別中成功選取了 UTF-8 語言環境,會輸出 UTF-8。命令 locale -m 提供一張已安裝的所有字元編碼名稱的列表。
如果您使用專門的 C 語言庫的多位元組函數來完成所有外部字元編碼和內部使用的 wchar_t 編碼之間的轉換,那麼 C 語言庫將承擔責任,根據 LC_CTYPE 使用正確的編碼方式。這甚至不需要程式被明確的編碼成當前的多位元組編碼。
如果需要一個應用程式能明確的支援 UTF-8(或其它編碼)轉換方法而不用 libc 多位元組函數,則應用程式必須確定是否需要啟用 UTF-8 模式。帶有 <langinfo.h> 庫標頭檔的與 X/Open 相容系統可以用如下代碼:
清單 10. 檢測當前的語言環境是否使用了 UTF-8 編碼
BOOL utf8_mode = FALSE;
if( ! strcmp(nl_langinfo(CODESET), "UTF-8") utf8_mode = TRUE;
|
為檢測當前語言環境是否使用了 UTF-8 編碼。首先必須調用 setlocale(LC_CTYPE, "") 函數,依據環境變數設定語言環境。nl_langinfo(CODESET) 函數也是由 locale charmap 命令調用,從而尋找當前語言環境指定的編碼名稱。
另一種可以使用的方法是查詢語言環境變數:
清單 11. 查詢語言環境變數
char *s; BOOL utf8_mode = FALSE;
if ((s = getenv("LC_ALL")) || (s = getenv("LC_CTYPE")) || (s = getenv ("LANG")))
{ if (strstr(s, "UTF-8")) utf8_mode = TRUE; }
|
這項測試假設 UTF-8 語言環境名稱中有值"UTF-8",但實際情況並不總是如此,所以應該使用 nl_langinfo() 方法。
總結
為
支援世界上的所有語言,需要一種具有八位位元組字元編碼策略的字元編碼系統,它的字元應多於 ASCII(一種使用無符號位元組的擴充版本)的 2^8 =
256 個字元。Unicode 就是這樣一種字元編碼系統,它具有由 128 個三維組(帶有由大量字元編碼方案的方法支援的 94,140
個定義好的字元值)組成的四維編碼空間,在 Linux 中更流行的字元編碼方案是 Unicode 轉換格式 UTF-8。
原文地址
http://qilong007.bokee.com/588575.html