[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

[轉寄] Java 編程技術中漢字問題的分析及解決(轉自IBM)



發信人: shaw (旺仔), 信區: JAVA
標  題: Java 編程技術中漢字問題的分析及解決(轉自IBM)
發信站: 碧海青天 (Sun Dec 24 21:12:02 2000), 轉信

Java 編程技術中漢字問題的分析及解決
段明輝
自由撰稿人
2000 年 11月 8日
----------------------------------------------------------------------------
----
在基于 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 和 UNI
CODE。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 。很多操作系統及應用軟件為了能夠更好的支持 Ja
va ,都內嵌了 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 只起調入 FR
AME 類等主程序的作用。界面包括 Textfield ,TextArea,List,Choice 等。
I.       取中文
用 JDBC 執行 SELECT 語句從服務器端讀取數據(中文)後,將數據用 APPEND 方法加
到 TextArea(TA) ,不能正確顯示。但加到 List 中時,大部分漢字卻可正確顯示。

將數據按“ISO-8859-1” 編碼方式轉化為字節數組,再按系統缺省編碼方式 (Defaul
t 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 情況下遇到的問題得到了很好的解決,測試的步
驟同上,有興趣的讀者可以嘗試一下。
[/b]Java 中文問題的根源分析及解決[/b]
在簡體中文 MS Windows 98 + JDK 1.3 下,可以用 System.getProperties() 得到 Ja
va 運行環境的一些基本屬性,類 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(S
tringBuffer) 復制成 byte bs[] ,然後 return new String(bs,”GB2312”)。作上
述修改後就需要自己解碼了:
HashTable form=HttpUtils .parseQueryString(request.getQueryString())或者
form=HttpUtils.parsePostData(……)
千萬別忘了編譯後放到 Servlet.jar 裡面。
五、 關于 Java 中文問題的總結
Java 編程語言成長于網絡世界,這就要求 Java 對多國字符有很好的支持。 Java 編程
語言適應了計算的網絡化的需求,為它能夠在網絡世界迅速成長奠定了堅實的基礎。 J
ava 的締造者 (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");
                        temp=in.getBytes("iso-8859-1");
                        s = new String(temp);
                }
                {
                        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;
        }
----------------------------------------------------------------------------
----
作者簡介
段明輝,清華大學電子工程系學生
現在正在清華大學微電子學研究所從事 Java 智能卡微處理器的研究和開發
領導 BBS 水木清華站的 Java 討論組,為眾多 Java 技術應用者提供解決方案

--
          ,        ,
         '(        )\
         | \   ___/ /
         `  \-'  _ -\
         /\   / / \/\)
        /     ' |  \ \

※ 來源:.碧海青天 bbs.dlut.edu.cn.[FROM: 202.118.68.59]
--
※ 轉寄:.碧海青天 bbs.dlut.edu.cn.[FROM: palm]

-- 
| This message was re-posted from debian-chinese-gb@lists.debian.org
| and converted from gb2312 to big5 by an automatic gateway.



Reply to: