1. 問題的提出。
各種各樣的編碼永遠是軟體開發人員最為頭痛的問題之一,Unicode為統一編碼帶來了希望。可是,就算是Unicode也不是百分百的完美,它只是完成了對各種語言編碼的制定,而在具體的作業系統支援上,又分為UTF8,UTF16和UTF32好幾個版本。比如,Windows系統支援的Unicode是UTF16,也就是每2個位元組表示一個字元(還有一種稱為代理的情況,容後討論)。而Linux下預設支援的卻是UTF32的Unicode標準,每4個位元組才表示一個字元。
2. 執行個體說明
想象一下,假設您在Windows 系統下開發了一個軟體,將一些文本資料用Unicode(UTF16)編碼儲存到一個檔案,現在由於業務需要,您想將這個程式在Linux平台跑起來,會發生什麼情況呢?
一般來說,您的代碼在處理字串資源時,會使用C/C++標準庫的wchar_t數組或者std::wstring來儲存字串,使用wstrcpy,wstrlen或者std::string類提供的成員函數處理字串,然後使用wfprintf或者std::wfstream進行字串的讀寫。這部分代碼好像沒有問題,標準的C/C++ 函數,在整個軟體移植時都不用修改。是的,代碼詞法和文法確實都沒有問題,編譯也能夠通過。可是,程式啟動並執行結果呢?根據上面的討論,wchar_t在Windows平台下,,每個字元2個位元組;而在linux平台下,每個字元4個位元組。如果您的某個使用者在Windows平台下儲存一個字串“Hello”到某個檔案,然後另一個使用者在Linux下開啟這個檔案,那麼,他所得到會是什麼呢?
Windows平台下用Unicode(UTF16)編碼儲存“Hello“到檔案,實際寫入的16進位串如下所示
H e l l o
4800 6500 6C00 6C00 6F00
形成一個串,00480065006C006C006F,寫入檔案。
在Linux系統下,程式將這個串當作UTF32讀入後,結果是兩個字元(最後2個位元組6F00被捨棄)
0x00650048和0x006C006C,字串長度是2,這顯然不是希望的到的“Hello“。
3. 解決的辦法
根據個人的經驗,解決方案主要有以下2種:
1. 由於產生平台不相容的主要原因是平台之間使用的Unicode方案不統一,一個最為顯而易見的解決辦法就是選擇使用某種作業系統的編碼,在另一個平台上,使用自己的字串儲存和操作函數。一般推薦使用UTF16,具體原因請參考本人關於UTF16和UTF32的比較。決定好使用的編碼方案後,還需要定義相應的操作函數,如字串拷貝函數,比較函數等。一個偷懶的辦法是把VC crt的部分代碼Copy出來,什麼wstrcpy,wstrlen,一個也不放過。在程式中進行字串處理和存取時使用自己定義的方法,就可以避免平台差異帶來的問題了。
而且,選擇這種方案的一個更為重要的原因是,這個解決方案已經有大量的Open Source軟體庫可以直接使用,比如ICU(International Component for Unicode),libiconv等。
2. 避開UTF16和UTF32,直接使用UTF8進行字串的處理,因為各個平台對於UTF8的處理都是完全相同的。而且,UTF8和UTF16和UTF32使用的編碼完全一樣,只是記憶體表示的差異,有成熟的演算法可以實現這3種編碼的直接轉換.以下是www.unicode.org官方的轉碼串連:http://www.unicode.org/Public/PROGRAMS/CVTUTF/。
當然,這種做法也有一定的不利因素,因為UTF8本身是一種可變長的編碼方案,對於常見的英文,使用一個位元組,對於中文,一般是3個位元組。這種可變長的字元編碼給一些操作帶來不便。如取某個字串的子串,就必須從頭開始做遍曆,然後一個一個字元進行計算,直到長度達到要求。而如果使用UTF16或者UTF32,簡單的對數組進行索引就可以了(不考慮UTF16的代理機制,常見文字不會用到)。
關於UTF8,UTF16,UTF32的詳細討論可以參考www.unicode.org,也可以參考本人blog編碼檔案夾下的一些介紹性文字。
4. 參考文檔
www.unicode.org,本文提及的所有資料都可以在unicode.org上找到。
http://oss.software.ibm.com/icu/,IBM的開源Unicode實現,可以完全替代作業系統提供的與編碼和國際化相關的功能。
http://www.gnu.org/software/libiconv/, GNU的Unicode實現,整合在Linux作業系統中,但是庫本身跨平台。