[轉貼]解析Java的中文問題

Home Home
引用 | 編輯 t0306894
2005-02-14 14:39
樓主
推文 x0
儘管關於Java中文問題的討論已經相當多了,但由於Java的相關技術標準繁多,面向Java的Web伺服器、應用伺服器以及JDBC資料庫驅動等都沒有官方的標準,所以Java應用在處理中文時所存在的問題不僅沒有消失而且隨著所選用的伺服器、驅動程式以及運行環境等因素的不同而變化。本專題中,我們整理了Java中文問題的分析文章及其解決方法,希望能幫您解開Java的中文之謎。


Java的中文問題
關於Java中文問題的幾條分析原則
雖然對於Java中文處理問題的討論已不乏其數,但我們如何從眾多現象中找出問題所在,並進行分析和解決呢?本文將主要從如何預測、發現和檢查問題的角度給出建議...

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

深入剖析JSP和Servlet對中文的處理
在Java程式中都曾遇到輸入的中文不能正確顯示的問題,本文就針對這種“中文問題”,給出了一種解決方法...




看過本次專題,希望您會找到對Java的中文問題的處理辦法。當然,我們也希望您能就次發表您的看法。如果您也更好的解決辦法、實戰經驗或者對Java的中文問題有更深入的理解,歡迎您參與交流,也歡迎您投稿,續寫本次專題。

關於Java中文問題的幾條分析原則
作者:周競濤 王明微 本文選自:IBM DW中國

儘管對於Java中文處理問題的討論已不乏其數,但由於Java技術涉及內容廣(J2EE包含了十幾種相關技術),技術供應商繁多,面向Java的Web 伺服器、應用伺服器以及JDBC資料庫驅動等都沒有官方的標準,所以Java應用在處理中文過程中出了存在固有的問題外也存在隨著選用的伺服器,驅動程式的不同而帶來的Java中文問題的多變性,增加了問題的複雜度。那麼,我們如何在這麼紛繁的現象中找到問題的癥結呢?
Java中文問題的一般解決辦法


事實上,Java的中文問題都是由於Java應用所採用的缺省編碼格式與目標或者應用所要讀入字元的編碼格式不同而造成的(具體參見文獻1)。對於如何解決Java的中文問題,通常有四種方法:

1) 選擇JDK的中文本地化版本。儘管Java2 JDK的中文本地化版本(http://java.sun.com/products/jdk/1.2/chinesejdk.html)並不是一個官方的版本,Sun公司也沒有承諾會對該本地化版本進行升級,但其仍不失為一個Java中文問題的解決方案。

2)選擇合適的編譯參數。對於Java的國際版本來講,我們也可以在編譯Java應用的時候通過指定確定的編碼機制來實現其編譯結果對中文的支援。例如,對於需要支援繁體中文和簡體中文應用可以通過javac -encoding big5 sourcefile.java 和javac -encoding gb2312 sourcefile.java來編譯根源程式。

3) 通過編程的方式實現字元編碼的轉換代碼。通過編程的方式來解決Java的中文問題,已經成為了一種較為普遍的做法。下面就是一種最常見的字元編碼轉換函數,其將字元的編碼格式轉換為中文Windows系統的GBK編碼形式。


public static String toChinese(String strvalue)
{
try{
if(strvalue==null)
return null;
else
{
strvalue = new String(strvalue.getBytes("ISO8859_1"), "GBK");
return strvalue;
}
}catch(Exception e){
return null;
}
}


4) 定義字元輸出集。對於JSP應用,我們可以通過


<%@ page contentType="text/html; charset=GBK" %>

<%@ page contentType="text/html; charset=GB2312" %>


來定義JSP頁面的字元輸出集。當然,我們也可以通過HTML的標記


<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=gb2312">


來定義字元的輸出集。
存在的問題


根據方法實現的方式,我們可以將以上四種方法分為兩類,一類是通過利用某些標準或者規則來實現的方法,上面的1)、2)、4)都屬於此類;一類是通過針對性的編程來實現的方法,上面所提的方法3)就屬於此類。

由於方法1),2),4)是具有規範性的一類方法,所以方法比較簡單,解決方案也不具備較大的針對性,較為通用,例如我們可以採用方法2)的編譯方式通過編譯Java原始檔案來實現內碼的預置,而無需考慮源碼到底有哪些部分出現了Java的中文處理問題,諸如輸出亂碼等等。

但是,正由於這些方法不具備針對性,解決問題的方法過於統一,所以在某些情況下,它們並不能徹底地解決Java的中文問題。舉一個非常常見的例子。在通常情況下,用戶的Java應用往往需要與其他Java應用介面進行交互,例如通過某種版本的JDBC訪問資料庫。由於JDBC的驅動所支援的編碼隨著提供商乃至版本的不同而不同,所以如果在資料庫的輸入輸出過程中出現中文不能正確處理問題時,我們需要在資料的輸入和輸出過程做兩次正好相反的編碼轉換,這對於方法1),2),4)來說,往往是無法解決的。當然,對於方法2,我們也可以通過採用一些技巧使來滿足上面的情況,一個最有效的辦法就是儘量將Java應用的各個部分元件化。例如我們可以通過將資料庫的讀入和輸出代碼分解在不同的原始檔案上來實現分別編譯,從而滿足不同的字元編碼要求。但是通常的程式設計都不太可能滿足這種要求,因為這種程式的劃分結果很可能是不合理的。例如,我們將資料庫的讀出和寫入方法封裝到一個類中是比較合適的一種設計,但如果將該類的這兩個方法分別實現在兩個檔裏則變得非常不合理。因此對於1),2),4)方法來說,雖然實現比較簡單,但卻具有一些無法克服的缺點。這也是那些實現起來相對複雜的編程方法得以流行的原因。

相對於方法1),2),4)來說,方法3)具有更好的針對性和靈活性。程式可以根據不同的情況做出靈活的處理,在任何需要的地方進行字元的編碼轉換,但是該方法的特點也對軟體的開發人員提出了更高要求--必須能夠準確的捕捉到有可能發生中文處理問題的地方,並做出正確的判斷和處理。
分析的原則


總的說來,所有解決Java中文處理的方法都不是很複雜。相反的是,由於Java技術特別是J2EE技術涉及的內容繁多,各種Web伺服器、應用伺服器以及JDBC資料庫驅動等參差不齊,所以如何正確而及時的發現應用的中文處理問題則變得相對複雜的多。那麼我們如何來發現這些問題呢?

通常,Java處理中文時所產生的問題都是由於用戶的Java應用所採用的缺省編碼格式與目標或者應用所要讀入字元的編碼格式不同而造成的,而引起這些不同的一個主要原因就是用戶的Java應用與其他應用進行了編碼格式不匹配的資料交換(包括直接或間接的資料登錄、輸出)。所以,為了及時發現問題,我們可以由這一點入手,根據以下的原則對應用進行分析:

注意字元變數情況。由於變數的字元編碼形式較為隱蔽,多次變數間數值的改變和運算可能會引起字元集的改變;在變數與頁面所提交資料的各種操作中,較容易發生不同編碼格式字元進行運算的情況。

注意任何形式的字元讀入與輸出。之所以要提到任何形式,是因為Java應用大多數都是作為網路應用開發的,所以與其他語言的應用相比,Java應用需要面對網路世界各種各樣的字元資料交換形式。例如各種表單的資料提交,URL形式的資料讀入,經過加密運算的字元資料交換,網頁控制項選擇結果的輸入,控制項內容的的顯示(如List控制項)等等。

小心使用第三方的元件和應用。由於第三方元件和應用的實現是非透明的,所以一般情況下,我們很難判斷這些元件或驅動的缺省編碼格式是什麼,也無法對其進行控制。因此,在使用它們所提供的介面函數進行資料交換的時候要特別注意,如果確實出現中文無法正確處理情況,應首先檢查我們自己的代碼並調整相關代碼以適應這些介面,因為這些元件或者應用基本上不會提供調整編碼機制的介面。必要時,我們可能需要採用其他可替換的元件或者應用。

注意被請求物件所含有的資料登錄與輸出。這是非常隱蔽的一類情況,當我們的應用以物件的方式(例如序列化的物件)進行交互時,如果這個物件內部含有字元資料的處理過程,或者含有某些資料的輸入、輸出,甚至是拋出一段用中文注解的異常,都可能出現中文無法正確顯示等問題。由於這些行為往往被封裝在物件中,所以我們在編寫程式時,很容易忽略這種可能情況。並且這種情況帶有一定的不可預見性,例如我們可能不清楚這個物件會在什麼時候拋出什麼樣的異常,所以這時我們就需要做一定的測試工作。

注意資料庫的資料訪問過程。Java通過JDBC與資料庫建立連接。對於JDBC驅動程式來說,由於目前大部分的JDBC驅動程式並不是針對中文系統而設計的(中文資料大都採用ISO-8859-1編碼方式),所以一般情況下在資料讀寫過程中往往都需要字元編碼的轉化。但是我們仍建議用戶在使用這些 JDBC驅動時,仔細閱讀它的說明。如果確實無法弄清JDBC字元資料的編碼到底是什麼,我們的建議是做一些必要的測試。例如下面是一組在簡體中文 Win2000平臺下,採用Weblogic 6.0所提供的JDBC驅動從MS SQL Server2000中正確讀入中文字元的代碼(例子中進行了字元運算):


...
Class.forName("weblogic.jdbc.mssqlserver4.Driver").newInstance();
conn = myDriver.connect("jdbc:weblogic:mssqlserver4", props);
conn.setCatalog("labmanager");
Statement st = conn.createStatement();
//execute a query
String testStr;
String testTempStr = new String() ;
testStr = new String(testTempStr.getBytes("ISO-8859-1"));//編碼轉化
DatabaseMetaData DBMetaData =conn.getMetaData();
ResultSet rs = DBMetaData.getTables(null, null,null,new
String[]{"TABLE"} );
while (rs.next()){
for(int j=1; j<=rs.getMetaData().getColumnCount(); j++){
testStr = testStr +String(rs.getObject(j).toString().getBytes("ISO-8859-1"));
}
}


然而,需要注意的是,不同的JDBC驅動對相同的資料庫的支援並不同,而同一類JDBC驅動對不同的資料庫的支援也不相同,也就是說我們的字元轉化代碼在 JDBC驅動改變甚至是版本變化情況下都有可能無法正確工作。例如對於上面的例子,在同樣的環境下改用i-net 的Una 2000 Driver Version 2.03 for MS SQL Server時,是無法正確處理中文的。原因很簡單,這個JDBC驅動本身支援的就是GBK的編碼機制,所以根本就不需要做任何的編碼轉化。

6)必要的測試。由於Java中文問題的產生隨著Web伺服器,流覽器,運行環境和開發工具的不同都可能發生變化,所以為了更好的避免問題的發生,我們必須作一些針對性的測試。另外,在我們確實無法通過分析來確定Java的中文處理問題是否可能發生的情況下或者無法知道問題的發生是由於哪個環節(是Web伺服器,流覽器還是JDBC資料驅動等等)引起的時候,測試工作則變得非常重要。並且我們可能需要較為全面的測試,例如對Web伺服器,流覽器和JDBC資料驅動等都要做測試,這樣有利於我們找出那些隱藏在多個環節協調過程中所產生的問題。
結論


事實上,Java中文處理之所以存在問題,其根本原因是由於被操作的中文字元(變數)的編碼格式與目標的編碼格式不同造成的,所有這些問題其實都是發生在字元的讀入、輸出過程中的,只要我們把握住這一環節,就可以更好的發現、分析、處理和預防Java的中文問題了。

參考資料

段明輝。Java 編程技術中漢字問題的分析及解決。http://www- 900.ibm.com/developerWorks/cn/java/java_chinese/index.shtml,該文分析了Java編程過程中中文問題產生的原因,並提出瞭解決方法。

作者簡介

周競濤,西北工業大學CAD/CAM國家專業實驗室博士研究生,致力於將哲學、數學結合到技術研究中,主要研究方向:中間件、XML技術、EII,Semantic Web Services。

王明微,陝西西安,西北工業大學CAD/CAM國家專業實驗室碩士研究生,研究方向:逆向工程、模式識別。

『引自 IBM DW中國』

JSP/Servlet 中的漢字編碼問題
作者:張建芳 本文選自:IBM DeveloperWorks 中國網站

網上就 JSP/Servlet 中 DBCS 字元編碼問題有許多優秀的文章和討論,本文對它們作一些整理,並結合 IBM WebSphere Application Server 3.5(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 中文字元集

其實解決 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 的支援,這是現階段和將來漢化的工作內容。

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

3.1 常見的 encoding 問題的現象

網上常出現的 JSP/Servlet encoding 問題一般都表現在 browser 或應用程式端,如:

●流覽器中看到的 Jsp/Servlet 頁面中的漢字怎麼都成了 ’?’ ?

●流覽器中看到的 Servlet 頁面中的漢字怎麼都成了亂碼?

●JAVA 應用程式介面中的漢字怎麼都成了方塊?

●Jsp/Servlet 頁面無法顯示 GBK 漢字。

●Jsp/Servlet 不能接收 form 提交的漢字。

●JSP/Servlet 資料庫讀寫無法獲得正確的內容。

隱藏在這些問題後面的是各種錯誤的字元轉換和處理(除第3個外,是因為 Java font 設置錯誤引起的)。解決類似的字元 encoding 問題,需要瞭解 Jsp/Servlet 的運行過程,檢查可能出現問題的各個點。

3.2 JSP/Servlet web 編程時的 encoding 問題

運行於Java 應用伺服器的 JSP/Servlet 為 Browser 提供 HTML 內容,其過程如下圖所示:

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

a.JSP 編譯。Java 應用伺服器將根據 JVM 的 file.encoding 值讀取 JSP 原始檔案,並轉換為內部字元編碼進行 JSP 編譯,生成 JAVA 原始檔案,根據 file.encoding 值寫回檔系統。如果當前系統語言支援 GBK,那麼這時候不會出現 encoding 問題。如果是英文的系統,如 LANG 是 en_US 的 Linux, AIX 或 Solaris,則要將 JVM 的 file.encoding 值置成 GBK 。系統語言如果是 GB2312,則根據需要,確定要不要設置 file.encoding,將 file.encoding 設為 GBK 可以解決潛在的 GBK 字元亂碼問題。

b.Java 需要被編譯為 .class 才能在 JVM 中執行,這個過程存在與a.同樣的 file.encoding 問題。從這裏開始 servlet 和 jsp 的運行就類似了,只不過 Servlet 的編譯不是自動進行的。

c.Servlet 需要將 HTML 頁面內容轉換為 browser 可接受的 encoding 內容發送出去。依賴於各 JAVA App Server 的實現方式,有的將查詢 Browser 的 accept-charset 和 accept-language 參數或以其他猜的方式確定 encoding 值,有的則不管。因此 constant-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 應當能夠確保在這個階段將嵌入的漢字正確傳送出去。

d.這是 URL 字元 encoding 問題。如果通過 get/post 方式從 browser 返回的值中包含漢字資訊, servlet 將無法得到正確的值。SUN的 J2SDK 中,HttpUtils.parseName 在解析參數時根本沒有考慮 browser 的語言設置,而是將得到的值按 byte 方式解析。這是網上討論得最多的 encoding 問題。因為這是設計缺陷,只能以 bin 方式重新解析得到的字串;或者以 hack HttpUtils 類的方式解決。參考文章 2、3 均有介紹,不過最好將其中的中文 encoding GB2312、 CP1381 都改為 GBK,否則遇到 GBK 漢字時,還是會有問題。

Servlet API 2.3 提供一個新的函數 HttpServeletRequest.setCharacterEncoding 用於在調用 request.getParameter(“param_name”) 前指定應用程式希望的 encoding,這將有助於徹底解決這個問題。

WebSphere Application Server 對標準的 Servlet API 2.x 作了擴展,提供較好的多語言支援。上述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。

3.3 資料庫讀寫時的 encoding 問題

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

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

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

3.4 定位問題時常用的技巧

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

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

其實 JSP/Servlet 的中文encoding 並沒有想像的那麼複雜,雖然定位和解決問題沒有定規,各種運行環境也各不儘然,但後面的原理是一樣的。瞭解字元集的知識是解決字元問題的基礎。不過,隨著中文字元集的變化,不僅僅是 java 編程,中文資訊處理中的問題還是會存在一段時間的。

獻花 x0