Java程式設計中漢字問題的分析及解決
在基於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 ?Cencoding GBK。
“ Java位元組碼->虛擬機器->作業系統”, Java執行環境(JRE)分英文版和國際版,但只有國際版才支援非英文字元。 Java開發工具包(JDK)肯定支援多國字元,但並非所有的計算機使用者都安裝了JDK 。很多作業系統及應用軟體為了能夠更好的支援Java ,都內嵌了JRE的國際版本,為自己支援多國字元提供了方便。
“作業系統->顯示裝置”,對於漢字來說,作業系統必須支援並能夠顯示它。英文作業系統如果不搭配特殊的應用軟體的話,是肯定不能夠顯示中文的。
還有一個問題,就是在Java程式設計過程中,對中文字元進行正確的編碼轉換。例如,向網頁輸出中文字串的時候,不論你是用
out.println(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 {}
執行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; } }} |
編碼轉化的過程如下:
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能夠嚴格地測試它的產品,為使用者帶來更多的方便。
附一個用於從資料庫和網路中取出中文亂碼的處理函式,入參是有問題的字串,出參是問題已經解決了的字串。