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

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

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

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




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

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


1.問題的起源

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

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

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

從理論上來說,這些根據字符集設置而進行的字符轉換不應該產生太多問題。而事實是由于應用程序的實際運行環境不同,Unicode和各個本地字符集的補充、完善,以及系統或應用程序實現的不規范,轉碼時出現的問題時時困擾著程序員和用戶。

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

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

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

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

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

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

JAVA支持的encoding中與中文編程相關的有:(有幾個在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(現在只有IBMJDK1.3.?有支持),同Cp1392,1392


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

3.中文轉碼時'?'、亂碼的由來

兩個方向轉換都有可能得到錯誤的結果:

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

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

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

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

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

4.2JSP/Servletweb編程時的encoding問題
運行于Java應用服務器的JSP/Servlet為Browser提供HTML內容,其過程如下圖所示:



其中有字符編碼轉換的地方有:

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


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


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


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

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

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

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

4.4數據庫讀寫時的encoding問題

JSP/Servlet編程中經常出現encoding問題的另一個地方是讀寫數據庫中的數據。

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

然后應當注意的是讀出來的數據的encoding,JAVA程序中一般得到的是Unicode。寫數據時則相反。

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

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

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

5.結束語

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


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



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

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

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

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

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

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

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

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

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

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

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

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

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

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

還是用

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

<%@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”編碼方式轉化成字節數組,而“GBK”是目標編碼方式。我們從以ISO-8859-1方式編碼的數據庫中讀出中文字符串s1,經過上述轉換過程,在支持GBK字符集的操作系統和應用軟件中就能夠正確顯示中文字符串s2。

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

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

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

I.取中文
用JDBC執行SELECT語句從服務器端讀取數據(中文)后,將數據用APPEND方法加到TextArea(TA),不能正確顯示。但加到List中時,大部分漢字卻可正確顯示。
將數據按“ISO-8859-1”編碼方式轉化為字節數組,再按系統缺省編碼方式(DefaultCharacterEncoding)轉化為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);

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

II.寫中文到數據庫
處理方式與“取中文”相逆,先將SQL語句按系統缺省編碼方式轉化為字節數組,再按“ISO-8859-1”編碼方式轉化為STRING,最后送去執行,則中文信息可正確寫入數據庫。

程序段如下:
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情況),上述程序代碼可正確執行。但是如果客戶機只有瀏覽器,而沒有JDK和CLASSPATH時(稱為B情況),則漢字無法正確轉換。

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

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

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

將該文件拷到服務器端和其它的類放在一起,并在程序的開頭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運行環境的一些基本屬性,類PoorChinese可以幫助我們得到這些屬性。
類PoorChinese的源代碼:

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

執行javaPoorChinese后,我們會得到:

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

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

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;
}
}
}

編碼轉化的過程如下:

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

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

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

找到源代碼HttpUtils中的staticprivateStringparseName,在返回前將sb(StringBuffer)復制成bytebs[>,然后returnnewString(bs,”GB2312”)。作上述修改后就需要自己解碼了:

HashTableform=HttpUtils.parseQueryString(request.getQueryString())或者
form=HttpUtils.parsePostData(……)

千萬別忘了編譯后放到Servlet.jar里面。

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

附一個用于從數據庫和網絡中取出中文亂碼的處理函數,入參是有問題的字符串,出參是問題已經解決了的字符串。
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;
}