Java的中文處理學習筆記:Hello Unicode

不知道你有沒有這樣的感受:爲什麼PHP很少有亂碼問題而用Java做WEB應用卻這麼麻煩呢?爲什麼在Google上能用簡體中文查到繁體中文,甚至日文的結果?而且用Google的時候發現它居然能自動根據我使用瀏覽器的語言選擇自動調出中文界面?

很多國際化應用的讓我理解了這麼一個道理:Unicode是爲更方便的做國際化應用設計的,而Java核心的字符是基於UNICODE的,這一機制爲應用提供了對中文“字”的控制(而不是字節)。但如果不仔細理解其中的規範,這種自由反而會成爲累贅,從而導致更多的亂碼問題:

  1. 關於字符集的一些基本概念;
  2. 試驗1:顯示系統的環境設置和支持的編碼方式;
  3. 試驗2:系統缺省編碼方式對Java應用的輸入輸出影響;
  4. 試驗3:在WEB應用中輸出和輸出中的字符集問題;

關於字符集的準備知識:
ISO-8859-1 GB2312 BIG5 GBK GB18030 UNICODE 爲什麼會有這麼多字符集編碼方式?

注意:以下說明不是嚴格定義,一些比喻僅作爲方便理解使用。

假設一個字符就是棋盤上的一個棋子,有其固定的座標,如果需要區別所有的字符,就需要有足夠的棋格容納不同的“字符”。 

英文和歐洲其他語言的單字節字符集(SingleByte Charsets):
首先對於ISO-8859系列的字符集都想象成一個:2^8 = 16 * 16 = 256個格子的棋盤,這樣所有的西文字符(英文)用這樣一個16×16的座標系就基本可以覆蓋全了。而英文實際上只用其中小於128(\x80)的部分就夠了。利用大於128部分的空間的不同定義規則形成了真對其他歐洲語言的擴展字符集:ISO-8859-2 ISO-8859-4等……

GB2312 BIG5 SJIS等多字節字符集(MultiByte Charsets):

對於亞洲語言來說:漢字這麼多,用這麼一個256格的小棋盤肯定放不下,所以要區別成千上萬的漢字解決辦法就是用2個字節(座標)來定位一個“字”在棋盤上的位置,將以上規則做一個擴展:

  • 如果第1個字符是小於128(\x80)的仍和英文字符集編碼方式保持兼容;
  • 如果第1個字符是大於128(\x80)的,就當成是漢字的第1個字節,這個自己和後面緊跟的1個字節組成一個漢字;

其結果相當於在位於128以上的小棋格里每個小棋格又劃分出了一個16×16的小棋盤。這樣一個棋盤中的格子數(可能容納的字符數)就變成了128 + 128 * 256。按照類似的方式有了簡體中文的GB2312標準,繁體中文的BIG5字符集和日文的SJIS字符集等,GB2312字符集包含大約有六仟多個常用簡體漢字。

由此可以看出,所有這些從ASCII擴展式的編碼方式中:英文部分都是兼容的,但擴展部分的編碼方式是不兼容的,雖然很多字在3種體系中寫法一致(比如“中文”這2個字)但在相應字符集中的座標不一致,所以GB2312編寫的頁面用BIG5看就變得面目全非了。而且有時候經常在瀏覽其他非英語國家的頁面時(比如包含有德語的人名時)經常出現奇怪的漢字,其實就是擴展位的編碼衝突造成的。

我把GBK和GB18030理解成一個小UNICODE:GBK字符集是GB2312的擴展(K),GBK裏大約有貳萬玖仟多個字符,除了保持和GB2312兼容外,繁體中文字,甚至連日文的假名字符也能顯示。而GB18030-2000則是一個更復雜的字符集,採用變長字節的編碼方式,能夠支持更多的字符。關於漢字的編碼方式比較詳細的定義規範可以參考:
http://www.unihan.com.cn/cjk/ana17.htm

ASCII(英文) ==> 西歐文字 ==> 東歐字符集(俄文,希臘語等) ==> 東亞字符集(GB2312 BIG5 SJIS等)==> 擴展字符集GBK GB18030這個發展過程基本上也反映了字符集標準的發展過程,但這麼隨着時間的推移,尤其是互聯網讓跨語言的信息的交互變得越來越多的時候,太多多針對本地語言的編碼標準的出現導致一個應用程序的國際化變得成本非常高。尤其是你要編寫一個同時包含法文和簡體中文的文檔,這時候一般都會想到要是用一個通用的字符集能夠顯示所有語言的所有文字就好了,而且這樣做應用也能夠比較方便的國際化,爲了達到這個目標,即使應用犧牲一些空間和程序效率也是非常值得的。UNICODE就是這樣一個通用的解決方案。

UNICODE雙字節字符集
所以你可以把UNICODE想象成這樣:讓所有的字符(包括英文)都用2個字節(2個8位)表示,這樣就有了一個2^(8*2) = 256 * 256 = 65536個格子的大棋盤。在這個棋盤中,這樣中(簡繁)日韓(還包括越南)文字作爲CJK字符集都放在一定的區位內,爲了減少重複,各種語言中寫法一樣的字共享一個“棋格”。詳細的區位見附錄A

Unicode:(DoubleByte Charsets)

什麼還要有UTF-8?畢竟互聯網70%以上的信息仍然是英文。如果連英文都用2個字節存取(UCS-2),空間浪費不就太多了?所謂UTF-8就是這樣一個爲了提高英文存取效率的字符集轉換格式:Unicode Transformation Form 8-bit form。用UTF-8,UNICODE的2字節字符用變長個(1-3個字節)表示:

  1. 對英文,仍然和ASCII一樣用1個字節表示,這個字節的值小於128(\x80);
  2. 對其他語言的用一個值位於128-256之間的字節開始,再加後面緊跟的2個字節表示,一個字符一共是3個字節;

因此,在應用中程序處理過程中所有字符都是16位(雙字節),但在存取轉換成字節流時使用UTF-8格式轉換,對於英文字符來說和原來用ASCII方式存取時相比大小仍然是一樣的,而對中文來說和原來的GB2312編碼方式相比,大小爲:(3字節/2字節)=1.5倍

小節:

假設英文字符集是一個16×16的棋盤,麼其他語言的字符集就是把高位區重新分割的(> 128)的中等棋盤,多種字符集之間互不兼容而UNICODE本身就相當於一個256×256的大棋盤,通過一定規則將英文和其他所有語言的字符都包含在內。

最需要注意的是JVM的file.encoding屬性,這個屬性確定了JVM的缺省的編碼/解碼方式:從而影響應用中所有字節流==>字符流的解碼方式 ,字符流==>字節流的編碼方式。

    LINUX下的LOCALE可以通過 LANG=zh_CN; LC_ALL=zh_CN.GBK; export LANG LC_ALL 設置。locale 命令可以顯示系統當前的環境設置
    Windows的LOCALE可以通過 控制面板==>區域設置 設置實現

結論1:

JVM的缺省編碼方式由系統的“本地語言環境”設置確定,和操作系統的類型無關。所以當設置成相同的LOCALE時,Linux和Windows下的缺省編碼方式是沒有區別的(可以認爲cp1252=ISO-8859-1都是一樣的西文編碼方式,只包含255以下的拉丁字符),因此後面的測試2我只列出了GNU/Linux下LOCALE分別設置成zh_CN和en_US的測試結果輸出。以下測試如果在Windows下分別按照不同的區域和字符集設置後試驗的輸出是一樣的。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章