論壇 >> 編程探討                     閱讀數(shù): 1735
JSP/Servlet 中的漢字編碼問題(轉(zhuǎn))

作者:UB    時間:2002-06-14 10:51:10 

JSP/Servlet中的漢字編碼問題(1)

[作者:不詳添加時間:2001-9-68:12:47>




網(wǎng)上就JSP/Servlet中DBCS字符編碼問題有許多優(yōu)秀的文章和討論,本文對它們作一些整理,并結(jié)合IBMWebSphereApplicationServer3.5(WAS)的解決方法作一些說明,希望它不是多余的。

內(nèi)容:
問題的起源
GB2312-80,GBK,GB18030-2000漢字字符集及Encoding
中文轉(zhuǎn)碼時'?'、亂碼的由來
JSP/Servlet漢字編碼問題及在WAS中的解決辦法
結(jié)束語


1.問題的起源

每個國家(或區(qū)域)都規(guī)定了計算機信息交換用的字符編碼集,如美國的擴展ASCII碼,中國的GB2312-80,日本的JIS等,作為該國家/區(qū)域內(nèi)信息處理的基礎(chǔ),有著統(tǒng)一編碼的重要作用。字符編碼集按長度分為SBCS(單字節(jié)字符集),DBCS(雙字節(jié)字符集)兩大類。早期的軟件(尤其是操作系統(tǒng)),為了解決本地字符信息的計算機處理,出現(xiàn)了各種本地化版本(L10N),為了區(qū)分,引進了LANG,Codepage等概念。但是由于各個本地字符集代碼范圍重疊,相互間信息交換困難;軟件各個本地化版本獨立維護成本較高。因此有必要將本地化工作中的共性抽取出來,作一致處理,將特別的本地化處理內(nèi)容降低到最少。這也就是所謂的國際化(I18N)。各種語言信息被進一步規(guī)范為Locale信息。處理的底層字符集變成了幾乎包含了所有字形的Unicode。

現(xiàn)在大部分具有國際化特征的軟件核心字符處理都是以Unicode為基礎(chǔ)的,在軟件運行時根據(jù)當時的Locale/Lang/Codepage設(shè)置確定相應(yīng)的本地字符編碼設(shè)置,并依此處理本地字符。在處理過程中需要實現(xiàn)Unicode和本地字符集的相互轉(zhuǎn)換,甚或以Unicode為中間的兩個不同本地字符集的相互轉(zhuǎn)換。這種方式在網(wǎng)絡(luò)環(huán)境下被進一步延伸,任何網(wǎng)絡(luò)兩端的字符信息也需要根據(jù)字符集的設(shè)置轉(zhuǎn)換成可接受的內(nèi)容。

Java語言內(nèi)部是用Unicode表示字符的,遵守UnicodeV2.0。Java程序無論是從/往文件系統(tǒng)以字符流讀/寫文件,還是往URL連接寫HTML信息,或從URL連接讀取參數(shù)值,都會有字符編碼的轉(zhuǎn)換。這樣做雖然增加了編程的復(fù)雜度,容易引起混淆,但卻是符合國際化的思想的。

從理論上來說,這些根據(jù)字符集設(shè)置而進行的字符轉(zhuǎn)換不應(yīng)該產(chǎn)生太多問題。而事實是由于應(yīng)用程序的實際運行環(huán)境不同,Unicode和各個本地字符集的補充、完善,以及系統(tǒng)或應(yīng)用程序?qū)崿F(xiàn)的不規(guī)范,轉(zhuǎn)碼時出現(xiàn)的問題時時困擾著程序員和用戶。

2.GB2312-80,GBK,GB18030-2000漢字字符集及Encoding

其實解決JAVA程序中的漢字編碼問題的方法往往很簡單,但理解其背后的原因,定位問題,還需要了解現(xiàn)有的漢字編碼和編碼轉(zhuǎn)換。

GB2312-80是在國內(nèi)計算機漢字信息技術(shù)發(fā)展初始階段制定的,其中包含了大部分常用的一、二級漢字,和9區(qū)的符號。該字符集是幾乎所有的中文系統(tǒng)和國際化的軟件都支持的中文字符集,這也是最基本的中文字符集。其編碼范圍是高位0xa1-0xfe,低位也是0xa1-0xfe;漢字從0xb0a1開始,結(jié)束于0xf7fe;

GBK是GB2312-80的擴展,是向上兼容的。它包含了20902個漢字,其編碼范圍是0x8140-0xfefe,剔除高位0x80的字位。其所有字符都可以一對一映射到Unicode2.0,也就是說JAVA實際上提供了GBK字符集的支持。這是現(xiàn)階段Windows和其它一些中文操作系統(tǒng)的缺省字符集,但并不是所有的國際化軟件都支持該字符集,感覺是他們并不完全知道GBK是怎么回事。值得注意的是它不是國家標準,而只是規(guī)范。隨著GB18030-2000國標的發(fā)布,它將在不久的將來完成它的歷史使命。

GB18030-2000(GBK2K)在GBK的基礎(chǔ)上進一步擴展了漢字,增加了藏、蒙等少數(shù)民族的字形。GBK2K從根本上解決了字位不夠,字形不足的問題。它有幾個特點,

它并沒有確定所有的字形,只是規(guī)定了編碼范圍,留待以后擴充。
編碼是變長的,其二字節(jié)部分與GBK兼容;四字節(jié)部分是擴充的字形、字位,其編碼范圍是首字節(jié)0x81-0xfe、二字節(jié)0x30-0x39、三字節(jié)0x81-0xfe、四字節(jié)0x30-0x39。
它的推廣是分階段的,首先要求實現(xiàn)的是能夠完全映射到Unicode3.0標準的所有字形。
它是國家標準,是強制性的。
現(xiàn)在還沒有任何一個操作系統(tǒng)或軟件實現(xiàn)了GBK2K的支持,這是現(xiàn)階段和將來漢化的工作內(nèi)容。
Unicode的介紹......就免了吧。

JAVA支持的encoding中與中文編程相關(guān)的有:(有幾個在JDK文檔中未列出)ASCII7-bit,同ascii7
ISO8859-18-bit,同8859_1,ISO-8859-1,ISO_8859-1,latin1...
GB2312-80同gb2312,gb2312-1980,EUC_CN,euccn,1381,Cp1381,1383,Cp1383,ISO2022CN,ISO2022CN_GB......
GBK(注意大小寫),同MS936
UTF8UTF-8
GB18030(現(xiàn)在只有IBMJDK1.3.?有支持),同Cp1392,1392


JAVA語言采用Unicode處理字符.但從另一個角度來說,在java程序中也可以采用非Unicode的轉(zhuǎn)碼,重要的是保證程序入口和出口的漢字信息不失真。如完全采用ISO-8859-1來處理漢字也能達到正確的結(jié)果。網(wǎng)絡(luò)上流行的許多解決方法,都屬于這種類型。為了不致引起混淆,本文不對這種方法作討論。

3.中文轉(zhuǎn)碼時'?'、亂碼的由來

兩個方向轉(zhuǎn)換都有可能得到錯誤的結(jié)果:

Unicode-->Byte,如果目標代碼集不存在對應(yīng)的代碼,則得到的結(jié)果是0x3f.
如:
"\u00d6\u00ec\u00e9\u0046\u00bb\u00f9".getBytes("GBK")的結(jié)果是"?ìéF?ù",Hex值是3fa8aca8a6463fa8b4.
仔細看一下上面的結(jié)果,你會發(fā)現(xiàn)\u00ec被轉(zhuǎn)換為0xa8ac,\u00e9被轉(zhuǎn)換為\xa8a6...它的實際有效位變長了!這是因為GB2312符號區(qū)中的一些符號被映射到一些公共的符號編碼,由于這些符號出現(xiàn)在ISO-8859-1或其它一些SBCS字符集中,故它們在Unicode中編碼比較靠前,有一些其有效位只有8位,和漢字的編碼重疊(其實這種映射只是編碼的映射,在顯示時仔細不是一樣的。Unicode中的符號是單字節(jié)寬,漢字中的符號是雙字節(jié)寬).在Unicode\u00a0--\u00ff之間這樣的符號有20個。了解這個特征非常重要!由此就不難理解為什么JAVA編程中,漢字編碼的錯誤結(jié)果中常常會出現(xiàn)一些亂碼(其實是符號字符),而不全是'?'字符,就比如上面的例子。

Byte-->Unicode,如果Byte標識的字符在源代碼集不存在,則得到的結(jié)果是0xfffd.
如:
Byteba[>={(byte)0x81,(byte)0x40,(byte)0xb0,(byte)0xa1};newString(ba,"gb2312");
結(jié)果是"?啊",hex值是"\ufffd\u554a".0x8140是GBK字符,按GB2312轉(zhuǎn)換表沒有對應(yīng)的值,取\ufffd.(請注意:在顯示該uniCode時,因為沒有對應(yīng)的本地字符,所以也適用上一種情況,顯示為一個"?".)

實際編程中,JSP/Servlet程序得到錯誤的漢字信息,往往是這兩個過程的疊加,有時甚至是兩個過程疊加后反復(fù)作用的結(jié)果.

4.JSP/Servlet漢字編碼問題及在WAS中的解決辦法

4.1常見的encoding問題的現(xiàn)象
網(wǎng)上常出現(xiàn)的JSP/Servletencoding問題一般都表現(xiàn)在browser或應(yīng)用程序端,如:
瀏覽器中看到的Jsp/Servlet頁面中的漢字怎么都成了’?’?
瀏覽器中看到的Servlet頁面中的漢字怎么都成了亂碼?
JAVA應(yīng)用程序界面中的漢字怎么都成了方塊?
Jsp/Servlet頁面無法顯示GBK漢字。
JSP頁面中內(nèi)嵌在<%...%>,<%=...%>等Tag包含的JAVAcode中的中文成了亂碼,但頁面的其它漢字是對的。
Jsp/Servlet不能接收form提交的漢字。
JSP/Servlet數(shù)據(jù)庫讀寫無法獲得正確的內(nèi)容。
隱藏在這些問題后面的是各種錯誤的字符轉(zhuǎn)換和處理(除第3個外,是因為Javafont設(shè)置錯誤引起的)。解決類似的字符encoding問題,需要了解Jsp/Servlet的運行過程,檢查可能出現(xiàn)問題的各個點。

4.2JSP/Servletweb編程時的encoding問題
運行于Java應(yīng)用服務(wù)器的JSP/Servlet為Browser提供HTML內(nèi)容,其過程如下圖所示:



其中有字符編碼轉(zhuǎn)換的地方有:

JSP編譯。Java應(yīng)用服務(wù)器將根據(jù)JVM的file.encoding值讀?。燡SP源文件,編譯生成JAVA源文件,再根據(jù)file.encoding值寫回文件系統(tǒng)。如果當前系統(tǒng)語言支持GBK,那么這時候不會出現(xiàn)encoding問題。如果是英文的系統(tǒng),如LANG是en_US的Linux,AIX或Solaris,則要將JVM的file.encoding值置成GBK。系統(tǒng)語言如果是GB2312,則根據(jù)需要,確定要不要設(shè)置file.encoding,將file.encoding設(shè)為GBK可以解決潛在的GBK字符亂碼問題


Java需要被編譯為.class才能在JVM中執(zhí)行,這個過程存在與a.同樣的file.encoding問題。從這里開始servlet和jsp的運行就類似了,只不過Servlet的編譯不是自動進行的。對于JSP程序,對產(chǎn)生的JAVA中間文件的編譯是自動進行的(在程序中直接調(diào)用sun.tools.javac.Main類).因此如果在這一步出現(xiàn)問題的話,也要檢查encoding和OS的語言環(huán)境,或者將內(nèi)嵌在JSPJAVACode中的靜態(tài)漢字轉(zhuǎn)為Unicode,要么靜態(tài)文本輸出不要放在JAVAcode中。對于Servlet,javac編譯時手工指定-encoding參數(shù)就可以了。


Servlet需要將HTML頁面內(nèi)容轉(zhuǎn)換為browser可接受的encoding內(nèi)容發(fā)送出去。依賴于各JAVAAppServer的實現(xiàn)方式,有的將查詢Browser的accept-charset和accept-language參數(shù)或以其它猜的方式確定encoding值,有的則不管。因此采用固定encoding也許是最好的解決方法。對于中文網(wǎng)頁,可在JSP或Servlet中設(shè)置contentType="text/html;charset=GB2312";如果頁面中有GBK字符,則設(shè)置為contentType="text/html;charset=GBK",由于IE和Netscape對GBK的支持程度不一樣,作這種設(shè)置時需要測試一下。
因為16位JAVAchar在網(wǎng)絡(luò)傳送時高8位會被丟棄,也為了確保Servlet頁面中的漢字(包括內(nèi)嵌的和servlet運行過程中得到的)是期望的內(nèi)碼,可以用PrintWriterout=res.getWriter()取代ServletOutputStreamout=res.getOutputStream().PrinterWriter將根據(jù)contentType中指定的charset作轉(zhuǎn)換(ContentType需在此之前指定!);也可以用OutputStreamWriter封裝ServletOutputStream類并用write(String)輸出漢字字符串。
對于JSP,JAVAApplicationServer應(yīng)當能夠確保在這個階段將嵌入的漢字正確傳送出去。


這是解釋URL字符encoding問題。如果通過get/post方式從browser返回的參數(shù)值中包含漢字信息,servlet將無法得到正確的值。SUN的J2SDK中,HttpUtils.parseName在解析參數(shù)時根本沒有考慮browser的語言設(shè)置,而是將得到的值按byte方式解析。這是網(wǎng)上討論得最多的encoding問題。因為這是設(shè)計缺陷,只能以bin方式重新解析得到的字符串;或者以hackHttpUtils類的方式解決。參考文章2均有介紹,不過最好將其中的中文encodingGB2312、CP1381都改為GBK,否則遇到GBK漢字時,還是會有問題。
ServletAPI2.3提供一個新的函數(shù)HttpServeletRequest.setCharacterEncoding用于在調(diào)用request.getParameter(“param_name”)前指定應(yīng)用程序希望的encoding,這將有助于徹底解決這個問題。
4.3IBMWebsphereApplicationServer中的解決方法

WebSphereApplicationServer對標準的ServletAPI2.x作了擴展,提供較好的多語言支持。運行在中文的操作系統(tǒng)中,可以不作任何設(shè)置就可以很好地處理漢字。下面的說明只是對WAS是運行在英文的系統(tǒng)中,或者需要有GBK支持時有效。

上述c,d情況,WAS都要查詢Browser的語言設(shè)置,在缺省狀況下,zh,zh-cn等均被映射為JAVAencodingCP1381(注意:CP1381只是等同于GB2312的一個codepage,沒有GBK支持)。這樣做我想是因為無法確認Browser運行的操作系統(tǒng)是支持GB2312,還是GBK,所以取其小。但是實際的應(yīng)用系統(tǒng)還是要求頁面中出現(xiàn)GBK漢字,最著名的是朱總理名字中的“镕"(rong2,0xe946,\u9555),所以有時還是需要將Encoding/Charset指定為GBK。當然WAS中變更缺省的encoding沒有上面說的那么麻煩,針對a,b,參考文章5,在ApplicationServer的命令行參數(shù)中指定-Dfile.encoding=GBK即可;針對d,在ApplicationServer的命令行參數(shù)中指定-Ddefault.client.encoding=GBK。如果指定了-Ddefault.client.encoding=GBK,那么c情況下可以不再指定charset。

上面列出的問題中還有一個關(guān)于Tag<%...%>,<%=...%>中的JAVA代碼里包含的靜態(tài)文本未能正確顯示的問題,在WAS中的解決方法是除了設(shè)置正確的file.encoding,還需要以相同方法設(shè)置-Duser.language=zh-Duser.region=CN。這與JAVAlocale的設(shè)置有關(guān)。

4.4數(shù)據(jù)庫讀寫時的encoding問題

JSP/Servlet編程中經(jīng)常出現(xiàn)encoding問題的另一個地方是讀寫數(shù)據(jù)庫中的數(shù)據(jù)。

流行的關(guān)系數(shù)據(jù)庫系統(tǒng)都支持數(shù)據(jù)庫encoding,也就是說在創(chuàng)建數(shù)據(jù)庫時可以指定它自己的字符集設(shè)置,數(shù)據(jù)庫的數(shù)據(jù)以指定的編碼形式存儲。當應(yīng)用程序訪問數(shù)據(jù)時,在入口和出口處都會有encoding轉(zhuǎn)換。對于中文數(shù)據(jù),數(shù)據(jù)庫字符編碼的設(shè)置應(yīng)當保證數(shù)據(jù)的完整性.GB2312,GBK,UTF-8等都是可選的數(shù)據(jù)庫encoding;也可以選擇ISO8859-1(8-bit),那么應(yīng)用程序在寫數(shù)據(jù)之前須將16Bit的一個漢字或Unicode拆分成兩個8-bit的字符,讀數(shù)據(jù)之后則需將兩個字節(jié)合并起來,同時還要判別其中的SBCS字符。沒有充分利用數(shù)據(jù)庫encoding的作用,反而增加了編程的復(fù)雜度,ISO8859-1不是推薦的數(shù)據(jù)庫encoding。JSP/Servlet編程時,可以先用數(shù)據(jù)庫管理系統(tǒng)提供的管理功能檢查其中的中文數(shù)據(jù)是否正確。

然后應(yīng)當注意的是讀出來的數(shù)據(jù)的encoding,JAVA程序中一般得到的是Unicode。寫數(shù)據(jù)時則相反。

4.5定位問題時常用的技巧

定位中文encoding問題通常采用最笨的也是最有效的辦法——在你認為有嫌疑的程序處理后打印字符串的內(nèi)碼。通過打印字符串的內(nèi)碼,你可以發(fā)現(xiàn)什么時候中文字符被轉(zhuǎn)換成Unicode,什么時候Unicode被轉(zhuǎn)回中文內(nèi)碼,什么時候一個中文字成了兩個Unicode字符,什么時候中文字符串被轉(zhuǎn)成了一串問號,什么時候中文字符串的高位被截掉了……

取用合適的樣本字符串也有助于區(qū)分問題的類型。如:”aa啊aa丂aa”等中英相間、GB、GBK特征字符均有的字符串。一般來說,英文字符無論怎么轉(zhuǎn)換或處理,都不會失真(如果遇到了,可以嘗試著增加連續(xù)的英文字母長度)。

5.結(jié)束語

其實JSP/Servlet的中文encoding并沒有想像的那么復(fù)雜,雖然定位和解決問題沒有定規(guī),各種運行環(huán)境也各不盡然,但后面的原理是一樣的。了解字符集的知識是解決字符問題的基礎(chǔ)。不過,隨著中文字符集的變化,不僅僅是java編程,中文信息處理中的問題還是會存在一段時間的。


...........................UB修改于2002-06-14 10:52:07



RE:JAVA編程技術(shù)中漢字問題的分析及解決(轉(zhuǎn))

作者:UB    時間:2002-07-22 17:04:00    [修改]    [回復(fù)]    [刪除]

JAVA編程技術(shù)中漢字問題的分析及解決
文章來源:www.ibm.com
在基于Java語言的編程中,我們經(jīng)常碰到漢字的處理及顯示的問題。一大堆看不懂的亂碼肯定不是我們愿意看到的顯示效果,怎樣才能夠讓那些漢字正確顯示呢?Java語言默認的編碼方式是UNICODE,而我們中國人通常使用的文件和數(shù)據(jù)庫都是基于GB2312或者BIG5等方式編碼的,怎樣才能夠恰當?shù)剡x擇漢字編碼方式并正確地處理漢字的編碼呢?本文將從漢字編碼的常識入手,結(jié)合Java編程實例,分析以上兩個問題并提出解決它們的方案。

現(xiàn)在Java編程語言已經(jīng)廣泛應(yīng)用于互聯(lián)網(wǎng)世界,早在Sun公司開發(fā)Java語言的時候,就已經(jīng)考慮到對非英文字符的支持了。Sun公司公布的Java運行環(huán)境(JRE)本身就分英文版和國際版,但只有國際版才支持非英文字符。不過在Java編程語言的應(yīng)用中,對中文字符的支持并非如同JavaSoft的標準規(guī)范中所宣稱的那樣完美,因為中文字符集不只一個,而且不同的操作系統(tǒng)對中文字符的支持也不盡相同,所以會有許多和漢字編碼處理有關(guān)的問題在我們進行應(yīng)用開發(fā)中困擾著我們。有很多關(guān)于這些問題的解答,但都比較瑣碎,并不能夠滿足大家迫切解決問題的愿望,關(guān)于Java中文問題的系統(tǒng)研究并不多,本文從漢字編碼常識出發(fā),分析Java中文問題,希望對大家解決這個問題有所幫助。

漢字編碼的常識
我們知道,英文字符一般是以一個字節(jié)來表示的,最常用的編碼方法是ASCII。但一個字節(jié)最多只能區(qū)分256個字符,而漢字成千上萬,所以現(xiàn)在都以雙字節(jié)來表示漢字,為了能夠與英文字符分開,每個字節(jié)的最高位一定為1,這樣雙字節(jié)最多可以表示64K格字符。我們經(jīng)常碰到的編碼方式有GB2312、BIG5、UNICODE等。關(guān)于具體編碼方式的詳細資料,有興趣的讀者可以查閱相關(guān)資料。我膚淺談一下和我們關(guān)系密切的GB2312和UNICODE。GB2312碼,中華人民共和國國家標準漢字信息交換用編碼,是一個由中華人民共和國國家標準總局發(fā)布的關(guān)于簡化漢字的編碼,通行于中國大陸地區(qū)及新加坡,簡稱國標碼。兩個字節(jié)中,第一個字節(jié)(高字節(jié))的值為區(qū)號值加32(20H),第二個字節(jié)(低字節(jié))的值為位號值加32(20H),用這兩個值來表示一個漢字的編碼。UNICODE碼是微軟提出的解決多國字符問題的多字節(jié)等長編碼,它對英文字符采取前面加“0”字節(jié)的策略實現(xiàn)等長兼容。如“A”的ASCII碼為0x41,UNICODE就為0x00,0x41。利用特殊的工具各種編碼之間可以互相轉(zhuǎn)換。

Java中文問題的初步認識
我們基于Java編程語言進行應(yīng)用開發(fā)時,不可避免地要處理中文。Java編程語言默認的編碼方式是UNICODE,而我們通常使用的數(shù)據(jù)庫及文件都是基于GB2312編碼的,我們經(jīng)常碰到這樣的情況:瀏覽基于JSP技術(shù)的網(wǎng)站看到的是亂碼,文件打開后看到的也是亂碼,被Java修改過的數(shù)據(jù)庫的內(nèi)容在別的場合應(yīng)用時無法繼續(xù)正確地提供信息。

StringsEnglish=“apple”;
StringsChinese=“蘋果”;
Strings=“蘋果apple”;

sEnglish的長度是5,sChinese的長度是4,而s默認的長度是14。對于sEnglish來說,Java中的各個類都支持得非常好,肯定能夠正確顯示。但對于sChinese和s來說,雖然JavaSoft聲明Java的基本類已經(jīng)考慮到對多國字符的支持(默認UNICODE編碼),但是如果操作系統(tǒng)的默認編碼不是UNICODE,而是國標碼等。從Java源代碼到得到正確的結(jié)果,要經(jīng)過“Java源代碼->Java字節(jié)碼->;虛擬機->操作系統(tǒng)->顯示設(shè)備”的過程。在上述過程中的每一步驟,我們都必須正確地處理漢字的編碼,才能夠使最終的顯示結(jié)果正確。

“Java源代碼->Java字節(jié)碼”,標準的Java編譯器javac使用的字符集是系統(tǒng)默認的字符集,比如在中文Windows操作系統(tǒng)上就是GBK,而在Linux操作系統(tǒng)上就是ISO-8859-1,所以大家會發(fā)現(xiàn)在Linux操作系統(tǒng)上編譯的類中源文件中的中文字符都出了問題,解決的辦法就是在編譯的時候添加encoding參數(shù),這樣才能夠與平臺無關(guān)。用法是

javac–encodingGBK。

“Java字節(jié)碼->虛擬機->操作系統(tǒng)”,Java運行環(huán)境(JRE)分英文版和國際版,但只有國際版才支持非英文字符。Java開發(fā)工具包(JDK)肯定支持多國字符,但并非所有的計算機用戶都安裝了JDK。很多操作系統(tǒng)及應(yīng)用軟件為了能夠更好的支持Java,都內(nèi)嵌了JRE的國際版本,為自己支持多國字符提供了方便。

“操作系統(tǒng)->顯示設(shè)備”,對于漢字來說,操作系統(tǒng)必須支持并能夠顯示它。英文操作系統(tǒng)如果不搭配特殊的應(yīng)用軟件的話,是肯定不能夠顯示中文的。

還有一個問題,就是在Java編程過程中,對中文字符進行正確的編碼轉(zhuǎn)換。例如,向網(wǎng)頁輸出中文字符串的時候,不論你是用

out.println(string);//string是含中文的字符串

還是用

<%=string%>,都必須作UNICODE到GBK的轉(zhuǎn)換,或者手動,或者自動。在JSP1.0中,可以定義輸出字符集,從而實現(xiàn)內(nèi)碼的自動轉(zhuǎn)換。用法是

<%@pageContentType=”text/html;charset=gb2312”%>

但是在一些JSP版本中并沒有提供對輸出字符集的支持,(例如JSP0.92),這就需要手動編碼輸出了,方法非常多。最常用的方法是

Strings1=request.getParameter(“keyword”);
Strings2=newString(s1.getBytes(“ISO-8859-1”),”GBK”);

getBytes方法用于將中文字符以“ISO-8859-1”編碼方式轉(zhuǎn)化成字節(jié)數(shù)組,而“GBK”是目標編碼方式。我們從以ISO-8859-1方式編碼的數(shù)據(jù)庫中讀出中文字符串s1,經(jīng)過上述轉(zhuǎn)換過程,在支持GBK字符集的操作系統(tǒng)和應(yīng)用軟件中就能夠正確顯示中文字符串s2。

Java中文問題的表層分析及處理

背景
開發(fā)環(huán)境JDK1.15Vcafe2.0JPadPro
服務(wù)器端NTIISSybaseSystemJconnect(JDBC)
客戶端IE5.0Pwin98

CLASS文件存放在服務(wù)器端,由客戶端的瀏覽器運行APPLET,APPLET只起調(diào)入FRAME類等主程序的作用。界面包括Textfield,TextArea,List,Choice等。

I.取中文
用JDBC執(zhí)行SELECT語句從服務(wù)器端讀取數(shù)據(jù)(中文)后,將數(shù)據(jù)用APPEND方法加到TextArea(TA),不能正確顯示。但加到List中時,大部分漢字卻可正確顯示。
將數(shù)據(jù)按“ISO-8859-1”編碼方式轉(zhuǎn)化為字節(jié)數(shù)組,再按系統(tǒng)缺省編碼方式(DefaultCharacterEncoding)轉(zhuǎn)化為STRING,即可在TA和List中正確顯示。
程序段如下:

dbstr2=results.getString(1);
//AfterreadingtheresultfromDBserver,convertingittostring.
dbbyte1=dbstr2.getBytes(“iso-8859-1”);
dbstr1=newString(dbbyte1);

在轉(zhuǎn)換字符串時不采用系統(tǒng)默認編碼方式,而直接采用“GBK”或者“GB2312”,在A和B兩種情況下,從數(shù)據(jù)庫取數(shù)據(jù)都沒有問題。

II.寫中文到數(shù)據(jù)庫
處理方式與“取中文”相逆,先將SQL語句按系統(tǒng)缺省編碼方式轉(zhuǎn)化為字節(jié)數(shù)組,再按“ISO-8859-1”編碼方式轉(zhuǎn)化為STRING,最后送去執(zhí)行,則中文信息可正確寫入數(shù)據(jù)庫。

程序段如下:
sqlstmt=tf_input.getText();
//BeforesendingstatementtoDBserver,convertingittosqlstatement.
dbbyte1=sqlstmt.getBytes();
sqlstmt=newString(dbbyte1,”iso-8859-1”);
_stmt=_con.createStatement();
_stmt.executeUpdate(sqlstmt);
……

問題:如果客戶機上存在CLASSPATH指向JDK的CLASSES.ZIP時(稱為A情況),上述程序代碼可正確執(zhí)行。但是如果客戶機只有瀏覽器,而沒有JDK和CLASSPATH時(稱為B情況),則漢字無法正確轉(zhuǎn)換。

我們的分析:
1.經(jīng)過測試,在A情況下,程序運行時系統(tǒng)的缺省編碼方式為GBK或者GB2312。在B情況下,程序啟動時瀏覽器的JAVA控制臺中出現(xiàn)如下錯誤信息:
Can'tfindresourceforsun.awt.windows.awtLocalization_zh_CN
然后系統(tǒng)的缺省編碼方式為“8859-1”。

2.如果在轉(zhuǎn)換字符串時不采用系統(tǒng)缺省編碼方式,而是直接采用“GBK”或“GB2312”,則在A情況下程序仍然可正常運行,在B情況下,系統(tǒng)出現(xiàn)錯誤:
UnsupportedEncodingException。

3.在客戶機上,把JDK的CLASSES.ZIP解壓后,放在另一個目錄中,CLASSPATH只包含該目錄。然后一邊逐步刪除該目錄中的.CLASS文件,另一邊運行測試程序,最后發(fā)現(xiàn)在一千多個CLASS文件中,只有一個是必不可少的,該文件是:sun.io.CharToByteDoubleByte.class。

將該文件拷到服務(wù)器端和其它的類放在一起,并在程序的開頭IMPORT它,在B情況下程序仍然無法正常運行。

4.在A情況下,如果在CLASSPTH中去掉sun.io.CharToByteDoubleByte.class,則程序運行時測得默認編碼方式為“8859-1”,否則為“GBK”或“GB2312”。
如果JDK的版本為1.2以上的話,在B情況下遇到的問題得到了很好的解決,測試的步驟同上,有興趣的讀者可以嘗試一下。

Java中文問題的根源分析及解決
在簡體中文MSWindows98+JDK1.3下,可以用System.getProperties()得到Java運行環(huán)境的一些基本屬性,類PoorChinese可以幫助我們得到這些屬性。
類PoorChinese的源代碼:

publicclassPoorChinese{
publicstaticvoidmain(String[>args){
System.getProperties().list(System.out);
}
}

執(zhí)行javaPoorChinese后,我們會得到:

系統(tǒng)變量file.encoding的值為GBK,user.language的值為zh,user.region的值為CN,這些系統(tǒng)變量的值決定了系統(tǒng)默認的編碼方式是GBK。

在上述系統(tǒng)中,下面的代碼將GB2312文件轉(zhuǎn)換成Big5文件,它們能夠幫助我們理解Java中漢字編碼的轉(zhuǎn)化:

importjava.io.*;
importjava.util.*;

publicclassgb2big5{

staticintiCharNum=0;

publicstaticvoidmain(String[>args){
System.out.println("InputGB2312file,outputBig5file.");
if(args.length!=2){
System.err.println("Usage:jviewgb2big5gbfilebig5file");
System.exit(1);
}
StringinputString=readInput(args[0>);
writeOutput(inputString,args[1>);
System.out.println("NumberofCharactersinfile:"+iCharNum+".");
}

staticvoidwriteOutput(Stringstr,StringstrOutFile){
try{
FileOutputStreamfos=newFileOutputStream(strOutFile);
Writerout=newOutputStreamWriter(fos,"Big5");
out.write(str);
out.close();
}
catch(IOExceptione){
e.printStackTrace();
e.printStackTrace();
}
}

staticStringreadInput(StringstrInFile){
StringBufferbuffer=newStringBuffer();
try{
FileInputStreamfis=newFileInputStream(strInFile);
InputStreamReaderisr=newInputStreamReader(fis,"GB2312");
Readerin=newBufferedReader(isr);
intch;
while((ch=in.read())>-1){
iCharNum+=1;
buffer.append((char)ch);
}
in.close();
returnbuffer.toString();
}
catch(IOExceptione){
e.printStackTrace();
returnnull;
}
}
}

編碼轉(zhuǎn)化的過程如下:

ByteToCharGB2312CharToByteBig5
GB2312------------------>Unicode------------->Big5

執(zhí)行javagb2big5gb.txtbig5.txt,如果gb.txt的內(nèi)容是“今天星期三”,則得到的文件big5.txt中的字符能夠正確顯示;而如果gb.txt的內(nèi)容是“情人節(jié)快樂”,則得到的文件big5.txt中對應(yīng)于“節(jié)”和“樂”的字符都是符號“?”(0x3F),可見sun.io.ByteToCharGB2312和sun.io.CharToByteBig5這兩個基本類并沒有編好。

正如上例一樣,Java的基本類也可能存在問題。由于國際化的工作并不是在國內(nèi)完成的,所以在這些基本類發(fā)布之前,沒有經(jīng)過嚴格的測試,所以對中文字符的支持并不像JavaSoft所聲稱的那樣完美。前不久,我的一位技術(shù)上的朋友發(fā)信給我說,他終于找到了JavaServlet中文問題的根源。兩周以來,他一直為JavaServlet的中文問題所困擾,因為每面對一個含有中文字符的字符串都必須進行強制轉(zhuǎn)換才能夠得到正確的結(jié)果(這好象是大家公認的唯一的解決辦法)。后來,他確實不想如此繼續(xù)安分下去了,因為這樣的事情確實不應(yīng)該是高級程序員所要做的工作,他就找出Servlet解碼的源代碼進行分析,因為他懷疑問題就出在解碼這部分。經(jīng)過四個小時的奮斗,他終于找到了問題的根源所在。原來他的懷疑是正確的,Servlet的解碼部分完全沒有考慮雙字節(jié),直接把%XX當作一個字符。(原來JavaSoft也會犯這幺低級的錯誤?。?
如果你對這個問題有興趣或者遇到了同樣的煩惱的話,你可以按照他的步驟對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里面。

五、關(guān)于Java中文問題的總結(jié)
Java編程語言成長于網(wǎng)絡(luò)世界,這就要求Java對多國字符有很好的支持。Java編程語言適應(yīng)了計算的網(wǎng)絡(luò)化的需求,為它能夠在網(wǎng)絡(luò)世界迅速成長奠定了堅實的基礎(chǔ)。Java的締造者(JavaSoft)已經(jīng)考慮到Java編程語言對多國字符的支持,只是現(xiàn)在的解決方案有很多缺陷在里面,需要我們付諸一些補償性的措施。而世界標準化組織也在努力把人類所有的文字統(tǒng)一在一種編碼之中,其中一種方案是ISO10646,它用四個字節(jié)來表示一個字符。當然,在這種方案未被采用之前,還是希望JavaSoft能夠嚴格地測試它的產(chǎn)品,為用戶帶來更多的方便。

附一個用于從數(shù)據(jù)庫和網(wǎng)絡(luò)中取出中文亂碼的處理函數(shù),入?yún)⑹怯袉栴}的字符串,出參是問題已經(jīng)解決了的字符串。
StringparseChinese(Stringin)
{
Strings=null;
bytetemp[>;
if(in==null)
{
System.out.println("Warn:Chinesenullfounded!");
returnnewString("");
}
try
{
temp=in.getBytes("iso-8859-1");
s=newString(temp);
}
catch(UnsupportedEncodingExceptione)
{
System.out.println(e.toString());
}
returns;
}