內(nèi)容摘要:
Java對輸入輸入首先有一個“字節(jié)流”到“字符流”之間的編碼/解碼過程,這個設(shè)置是根據(jù)系統(tǒng)配置決定的,為什么PHP之類的應(yīng)用很少有字符集問題而Java有很好的國際化機制,卻經(jīng)常出現(xiàn)亂碼問題呢?
簡單的舉例:
有一個包含“你好”這2個中文字的文件實際上是4個字節(jié)組成的:C4 E3 BA C3
在英文操作系統(tǒng)中缺省的編碼解碼方式是缺省編碼方式是ISO8859,所以直接從文件中讀取的結(jié)果是4個的字節(jié),按ISO8859解碼后在程序中操作的是4個Java字符,雖然每個JAVA字符是16位Unicode,但每個字符仍是8位字節(jié)的映射\u00C4\u00E3\u00BA\u00C3,因此處理的仍是“英文”。而顯示過程中,是瀏覽器將字節(jié)流正確的顯示成了相應(yīng)的中文。
而一個Java應(yīng)用在GBK編碼方式的操作系統(tǒng)中,直接從文件中讀取4個的字節(jié)后,按GBK方式解碼后是2個16位的Java字符\u4F60\u597D,每個字都是相應(yīng)Unicode的CJK區(qū)塊所對應(yīng)的中文。
更多的例子請參考:Java的中文處理學(xué)習(xí)筆記
這也就是為什么在php等應(yīng)用很少出字符集問題的原因:在服務(wù)器端環(huán)境缺省一般是英文(ISO8859-1),等于全部處理使用的都是按字節(jié)方式處理的。數(shù)據(jù)輸入輸出過程中編碼方式完全不被改動,因此亂碼問題很少出現(xiàn)。而Java實際上提供了把每個中文直接當(dāng)成1個“字”而不是2個字節(jié)處理的機制,主要的亂碼問題往往是輸入輸出時編碼解碼方式不一致造成的。而且通過Unicode機制,程序除了實現(xiàn)程序界面根據(jù)本地化的適應(yīng)外,甚至程序處理的內(nèi)容本身的在不同字符集的操作系統(tǒng)中也是可以通用的,比如:在繁體中文操作系統(tǒng)中編輯的內(nèi)容,在簡體中文操作系統(tǒng)中也能正常的查詢。以下例子可能更說明問題:
通過GNU/Linux系統(tǒng)的本地化設(shè)置讓JAVA應(yīng)用支持中文
Java 編程技術(shù)中漢字問題的分析及解決這篇直到最近還經(jīng)常被一些網(wǎng)站轉(zhuǎn)貼的文章中有一個例子說明了很多中國程序員遇到漢字亂碼問題的思路:"GB2312 it"(漢化)
原文如下:
>>>>>>>
......前不久,我的一位技術(shù)上的朋友發(fā)信給我說,他終于找到了 Java Servlet 中文問題的根源。兩周以來,他一直為 Java Servlet 的中文問題所困擾,因為每面對一個含有中文字符的字符串都必須進行強制轉(zhuǎn)換才能夠得到正確的結(jié)果(這好象是大家公認的唯一的解決辦法)。后來,他確實不想如此繼續(xù)安分下去了,因為這樣的事情確實不應(yīng)該是高級程序員所要做的工作,他就找出 Servlet 解碼的源代碼進行分析,因為他懷疑問題就出在解碼這部分。經(jīng)過四個小時的奮斗,他終于找到了問題的根源所在。原來他的懷疑是正確的, Servlet 的解碼部分完全沒有考慮雙字節(jié),直接把 %XX 當(dāng)作一個字符。(原來 Java Soft 也會犯這幺低級的錯誤?。?/i>
如果你對這個問題有興趣或者遇到了同樣的煩惱的話,你可以按照他的步驟對 Servlet.jar 進行修改:
找到源代碼 HttpUtils 中的 static private String parseName ,在返回前將 sb(StringBuffer) 復(fù)制成 byte bs[] ,然后 return new String(bs,”GB2312”)。作上述修改后就需要自己解碼了:
HashTable form=HttpUtils .parseQueryString(request.getQueryString())或者
form=HttpUtils.parsePostData(……)
千萬別忘了編譯后放到 Servlet.jar 里面。
......
<<<<<<<<<
請問這位“高級”程序員幾個問題:
也許我錯了,但我的感覺是犯低級錯誤的不是JAVA:
如何設(shè)置可以讓GNU/Linux從系統(tǒng)層次就支持中文編碼(讓JVM缺省的file.encoding就按照中文GB2312或者GBK進行編碼解碼)呢?
以上這篇文章發(fā)表在2000年年底,當(dāng)時的GNU/Linux的內(nèi)核是基于glibc-2.1開發(fā)的,而GLIBC2.1對中文的locale支持還有限,因此,在GNU/Linux上不能根據(jù)locale的設(shè)置將系統(tǒng)缺省的編碼方式變成GB2312,從而改變JVM缺省的編碼方式。關(guān)于GNU/Linux對l10n的支持請看:GNU/Linux程序員必讀:中文化與GB18030標(biāo)準(zhǔn)。所以在redhat6.x下,無論你怎么設(shè)置locale,系統(tǒng)缺省的缺省file.encoding都是ISO_8859_1。
在redhat7.x 系統(tǒng)內(nèi)核所基于的glibc-2.2.x對l10n有了更完整的支持,所以可以通過設(shè)置
LC_ALL=zh_CN.GB2312;export LC_ALL
LANG=zh_CN.GB2312;export LANG
讓系統(tǒng)缺省的編碼方式變成GB2312。JVM會根據(jù)系統(tǒng)的缺省編碼方式設(shè)置系統(tǒng)的file.encoding屬性。之后,應(yīng)用中任何字節(jié)流到字符流的轉(zhuǎn)換,JVM都會按照這一系統(tǒng)缺省編碼方式進行轉(zhuǎn)換。因此在基于glibc-2.2以上的GNU/Linux上是可以通過locale的設(shè)置來改變系統(tǒng)缺省的編碼方式,從而改變上層Java應(yīng)用的缺省編碼、解碼方式的。
locale設(shè)置對JVM file.encoding的影響可以我通過做過的一個試驗說明:hello_unicode.html
由此可見:
通過web.xml設(shè)置解決URLEncoder.encode()方法和系統(tǒng)缺省編碼方式相關(guān)的問題
在我所理解的范圍內(nèi),J2SE 1.3中非常不符合Java的國際化規(guī)范的是在使用URLEncoder的時候:
比如在中文WIN98上運行的應(yīng)用,使用URLEncoder.encode(String s)時:比如“中文”這2個字符直接被Encoding的話結(jié)果是"%3F%3F"=>"??"。原因很簡單,“中文”在encode()過程中應(yīng)該先按GBK編碼方式編碼成4個字節(jié)(\u00d6\u00d0\u00ce\u00c4)后再進行URLEncoding才是正確的。這個在J2SE1.4中也修正了。方法encode(String s)已經(jīng)不鼓勵使用,取而代之的是除了需要進行URLEncoding的字符串外,同時需要指定字符串編碼方式的encode(String s, String enc)。這樣,URLEncoder就可以和系統(tǒng)缺省的編碼方式無關(guān)了。
在J2SE1.3下一個WEB應(yīng)用如果有這個問題可以通過在WEB-INF/web.xml中設(shè)置character-encoding來解決:
<web-app character-encoding="your_system_default_file.encoding">
...
</web-app>
比如:產(chǎn)品是在中文WINDOWS98中運行,系統(tǒng)缺省字符集是用GBK,則這個應(yīng)用的web.xml需要設(shè)置成:
<web-app character-encoding="GBK">
...
</web-app>
UniCode inside, Localization outsite
以上2個方法仍然只是讓應(yīng)用更方便地本地化了,而應(yīng)用本身并不是真正的國際化應(yīng)用。設(shè)想一下如何設(shè)計一個全球的論壇系統(tǒng):可以讓中文和日文的用戶都可以方便的瀏覽發(fā)表呢?在數(shù)據(jù)中間處理階段應(yīng)該以那種字符集存儲呢?答案很簡單:UniCode。以前很多文章都有關(guān)于如何設(shè)計一個國際化界面的介紹,只是應(yīng)用的本地化界面輸出,但很少提及數(shù)據(jù)在中間處理過程中如何適應(yīng)國際化。
輸入和存儲階段就用UniCode方式進行處理和存儲,以方便應(yīng)用以后的國際化。GOOGLE的設(shè)計就是一個非常好的國際化應(yīng)用榜樣,我以GOOGLE搜索引擎的國際化支持為例說明如何實現(xiàn)國際化應(yīng)用的設(shè)計。
GOOGLE用戶經(jīng)常有這樣的感覺:
首先我將GOOGLE對查詢的處理流程簡單的說明如下:
具體說明:
從以上的分析中我們可以看出:UniCode非常漂亮的解決了應(yīng)用的國際化問題。對于應(yīng)用前端來說,剩下的工作就是根據(jù)本地編碼環(huán)境進行本地化的過程了。
如果應(yīng)用的開發(fā)只是滿足于在國內(nèi)市場自給自足,“漢化”的思路的大量出現(xiàn)是很自然的。但要是把“漢化”比作UCDOS和RichWin的話,那么這種漢化方式遲早要被內(nèi)核漢化的WIN95淘汰的。畢竟核心級別對國際化的支持才是一個真正簡化前端應(yīng)用設(shè)計、通用的解決方案。Microsoft和Sun等國際化大公司的產(chǎn)品從一開始就是為全球市場設(shè)計的,因此對國際化的支持一致非常重視。相比之下國內(nèi)軟件行業(yè)對相應(yīng)國際標(biāo)準(zhǔn)顯然重視不足,也很少積極地參與相關(guān)標(biāo)準(zhǔn)制定。
參考文檔:
Java i18n
http://java.sun.com/docs/books/tutorial/i18n/index.html
Linux 國際化本地化和中文化
http://www.linuxforum.net/doc/i18n-new.html
GNU/Linux 程序員必讀:中文化與GB18030標(biāo)準(zhǔn)
http://www.ccidnet.com/tech/os/2001/07/31/58_2811.html
UniCode FAQ
http://www.cl.cam.ac.uk/~mgk25/UniCode.html
http://www.linuxforum.net/books/UTF-8-UniCode.html (中文版)
Java 編程技術(shù)中漢字問題的分析及解決
http://www-900.ibm.com/developerWorks/cn/java/java_chinese/index.shtml
漢字的編碼方式:
http://www.unihan.com.cn/cjk/ana17.htm
*注釋:l10n i18n都是縮寫:用的是英文單詞的首尾字母和其間字母個數(shù)
l10n: localization 本地化
i18n: internationalization 國際化