forget and forget

          能吃能睡是福;能忘是大福......

          posts - 39, comments - 26, trackbacks - 0, articles - 10
            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          Java中文問(wèn)題的一般解決辦法

          Posted on 2006-03-29 11:17 橘子 閱讀(795) 評(píng)論(0)  編輯  收藏 所屬分類: WEB開發(fā)

          漢字編碼的常識(shí)

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


          事實(shí)上,Java的中文問(wèn)題都是由于Java應(yīng)用所采用的缺省編碼格式與目標(biāo)或者應(yīng)用所要讀入字符的編碼格式不同而造成的(具體參見文獻(xiàn)1)。對(duì)于如何解決Java的中文問(wèn)題,通常有四種方法:

          1) 選擇JDK的中文本地化版本。盡管Java2 JDK的中文本地化版本(http://java.sun.com/products/jdk/1.2/chinesejdk.html)并不是一個(gè)官方的版本,Sun公司也沒(méi)有承諾會(huì)對(duì)該本地化版本進(jìn)行升級(jí),但其仍不失為一個(gè)Java中文問(wèn)題的解決方案。

          2) 選擇合適的編譯參數(shù)。對(duì)于Java的國(guó)際版本來(lái)講,我們也可以在編譯Java應(yīng)用的時(shí)候通過(guò)指定確定的編碼機(jī)制來(lái)實(shí)現(xiàn)其編譯結(jié)果對(duì)中文的支持。例如,對(duì)于需要支持繁體中文和簡(jiǎn)體中文應(yīng)用可以通過(guò)javac -encoding big5 sourcefile.java 和javac -encoding gb2312 sourcefile.java來(lái)編譯源程序。

          3) 通過(guò)編程的方式實(shí)現(xiàn)字符編碼的轉(zhuǎn)換代碼。通過(guò)編程的方式來(lái)解決Java的中文問(wèn)題,已經(jīng)成為了一種較為普遍的做法。下面就是一種最常見的字符編碼轉(zhuǎn)換函數(shù),其將字符的編碼格式轉(zhuǎn)換為中文Windows系統(tǒng)的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) 定義字符輸出集。對(duì)于JSP應(yīng)用,我們可以通過(guò)<%@ page contentType="text/html; charset=GBK" %>或<%@ page contentType="text/html; charset=GB2312" %>來(lái)定義JSP頁(yè)面的字符輸出集。當(dāng)然,我們也可以通過(guò)HTML的標(biāo)記<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=gb2312">來(lái)定義字符的輸出集。

          分析的原則

          總的說(shuō)來(lái),所有解決Java中文處理的方法都不是很復(fù)雜。相反的是,由于Java技術(shù)特別是J2EE技術(shù)涉及的內(nèi)容繁多,各種Web服務(wù)器、應(yīng)用服務(wù)器以及JDBC數(shù)據(jù)庫(kù)驅(qū)動(dòng)等參差不齊,所以如何正確而及時(shí)的發(fā)現(xiàn)應(yīng)用的中文處理問(wèn)題則變得相對(duì)復(fù)雜的多。那么我們?nèi)绾蝸?lái)發(fā)現(xiàn)這些問(wèn)題呢?

          通常,Java處理中文時(shí)所產(chǎn)生的問(wèn)題都是由于用戶的Java應(yīng)用所采用的缺省編碼格式與目標(biāo)或者應(yīng)用所要讀入字符的編碼格式不同而造成的,而引起這些不同的一個(gè)主要原因就是用戶的Java應(yīng)用與其它應(yīng)用進(jìn)行了編碼格式不匹配的數(shù)據(jù)交換(包括直接或間接的數(shù)據(jù)輸入、輸出)。所以,為了及時(shí)發(fā)現(xiàn)問(wèn)題,我們可以由這一點(diǎn)入手,根據(jù)以下的原則對(duì)應(yīng)用進(jìn)行分析:

          1. 注意字符變量情況。由于變量的字符編碼形式較為隱蔽,多次變量間數(shù)值的改變和運(yùn)算可能會(huì)引起字符集的改變;在變量與頁(yè)面所提交數(shù)據(jù)的各種操作中,較容易發(fā)生不同編碼格式字符進(jìn)行運(yùn)算的情況。
          2. 注意任何形式的字符讀入與輸出。之所以要提到任何形式,是因?yàn)镴ava應(yīng)用大多數(shù)都是作為網(wǎng)絡(luò)應(yīng)用開發(fā)的,所以與其它語(yǔ)言的應(yīng)用相比,Java應(yīng)用需要面對(duì)網(wǎng)絡(luò)世界各種各樣的字符數(shù)據(jù)交換形式。例如各種表單的數(shù)據(jù)提交,URL形式的數(shù)據(jù)讀入,經(jīng)過(guò)加密運(yùn)算的字符數(shù)據(jù)交換,網(wǎng)頁(yè)控件選擇結(jié)果的輸入,控件內(nèi)容的的顯示(如List控件)等等。
          3. 小心使用第三方的組件和應(yīng)用。由于第三方組件和應(yīng)用的實(shí)現(xiàn)是非透明的,所以一般情況下,我們很難判斷這些組件或驅(qū)動(dòng)的缺省編碼格式是什么,也無(wú)法對(duì)其進(jìn)行控制。因此,在使用它們所提供的接口函數(shù)進(jìn)行數(shù)據(jù)交換的時(shí)候要特別注意,如果確實(shí)出現(xiàn)中文無(wú)法正確處理情況,應(yīng)首先檢查我們自己的代碼并調(diào)整相關(guān)代碼以適應(yīng)這些接口,因?yàn)檫@些組件或者應(yīng)用基本上不會(huì)提供調(diào)整編碼機(jī)制的接口。必要時(shí),我們可能需要采用其它可替換的組件或者應(yīng)用。
          4. 注意被請(qǐng)求對(duì)象所含有的數(shù)據(jù)輸入與輸出。這是非常隱蔽的一類情況,當(dāng)我們的應(yīng)用以對(duì)象的方式(例如序列化的對(duì)象)進(jìn)行交互時(shí),如果這個(gè)對(duì)象內(nèi)部含有字符數(shù)據(jù)的處理過(guò)程,或者含有某些數(shù)據(jù)的輸入、輸出,甚至是拋出一段用中文注解的異常,都可能出現(xiàn)中文無(wú)法正確顯示等問(wèn)題。由于這些行為往往被封裝在對(duì)象中,所以我們?cè)诰帉懗绦驎r(shí),很容易忽略這種可能情況。并且這種情況帶有一定的不可預(yù)見性,例如我們可能不清楚這個(gè)對(duì)象會(huì)在什么時(shí)候拋出什么樣的異常,所以這時(shí)我們就需要做一定的測(cè)試工作。
          5. 注意數(shù)據(jù)庫(kù)的數(shù)據(jù)訪問(wèn)過(guò)程。Java通過(guò)JDBC與數(shù)據(jù)庫(kù)建立連接。對(duì)于JDBC驅(qū)動(dòng)程序來(lái)說(shuō),由于目前大部分的JDBC驅(qū)動(dòng)程序并不是針對(duì)中文系統(tǒng)而設(shè)計(jì)的(中文數(shù)據(jù)大都采用ISO-8859-1編碼方式),所以一般情況下在數(shù)據(jù)讀寫過(guò)程中往往都需要字符編碼的轉(zhuǎn)化。但是我們?nèi)越ㄗh用戶在使用這些JDBC驅(qū)動(dòng)時(shí),仔細(xì)閱讀它的說(shuō)明。如果確實(shí)無(wú)法弄清JDBC字符數(shù)據(jù)的編碼到底是什么,我們的建議是做一些必要的測(cè)試。例如下面是一組在簡(jiǎn)體中文Win2000平臺(tái)下,采用Weblogic 6.0所提供的JDBC驅(qū)動(dòng)從MS SQL Server2000中正確讀入中文字符的代碼(例子中進(jìn)行了字符運(yùn)算):
            ????
            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"));//編碼轉(zhuǎn)化
            ????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"));
            ????????????}

            ????????}
          6. 然而,需要注意的是,不同的JDBC驅(qū)動(dòng)對(duì)相同的數(shù)據(jù)庫(kù)的支持并不同,而同一類JDBC驅(qū)動(dòng)對(duì)不同的??數(shù)據(jù)庫(kù)的支持也不相同,也就是說(shuō)我們的字符轉(zhuǎn)化代碼在JDBC驅(qū)動(dòng)改變甚至是版本變化情況下都有可能無(wú)法正確工作。例如對(duì)于上面的例子,在同樣的環(huán)境下改用i-net 的Una 2000 Driver Version 2.03 for MS SQL Server時(shí),是無(wú)法正確處理中文的。原因很簡(jiǎn)單,這個(gè)JDBC驅(qū)動(dòng)本身支持的就是GBK的編碼機(jī)制,所以根本就不需要做任何的編碼轉(zhuǎn)化。?
          7. ?必要的測(cè)試。由于Java中文問(wèn)題的產(chǎn)生隨著Web服務(wù)器,瀏覽器,運(yùn)行環(huán)境和開發(fā)工具的不同都可能發(fā)生變化,所以為了更好的避免問(wèn)題的發(fā)生,我們必須作一些針對(duì)性的測(cè)試。另外,在我們確實(shí)無(wú)法通過(guò)分析來(lái)確定Java的中文處理問(wèn)題是否可能發(fā)生的情況下或者無(wú)法知道問(wèn)題的發(fā)生是由于哪個(gè)環(huán)節(jié)(是Web服務(wù)器,瀏覽器還是JDBC數(shù)據(jù)驅(qū)動(dòng)等等)引起的時(shí)候,測(cè)試工作則變得非常重要。并且我們可能需要較為全面的測(cè)試,例如對(duì)Web服務(wù)器,瀏覽器和JDBC數(shù)據(jù)驅(qū)動(dòng)等都要做測(cè)試,這樣有利于我們找出那些隱藏在多個(gè)環(huán)節(jié)協(xié)調(diào)過(guò)程中所產(chǎn)生的問(wèn)題。


          Java 的基本類也可能存在問(wèn)題。由于國(guó)際化的工作并不是在國(guó)內(nèi)完成的,所以在這些基本類發(fā)布之前,沒(méi)有經(jīng)過(guò)嚴(yán)格的測(cè)試,所以對(duì)中文字符的支持并不像 Java Soft 所聲稱的那樣完美。前不久,我的一位技術(shù)上的朋友發(fā)信給我說(shuō),他終于找到了 Java Servlet 中文問(wèn)題的根源。兩周以來(lái),他一直為 Java Servlet 的中文問(wèn)題所困擾,因?yàn)槊棵鎸?duì)一個(gè)含有中文字符的字符串都必須進(jìn)行強(qiáng)制轉(zhuǎn)換才能夠得到正確的結(jié)果(這好象是大家公認(rèn)的唯一的解決辦法)。后來(lái),他確實(shí)不想如此繼續(xù)安分下去了,因?yàn)檫@樣的事情確實(shí)不應(yīng)該是高級(jí)程序員所要做的工作,他就找出 Servlet 解碼的源代碼進(jìn)行分析,因?yàn)樗麘岩蓡?wèn)題就出在解碼這部分。經(jīng)過(guò)四個(gè)小時(shí)的奮斗,他終于找到了問(wèn)題的根源所在。原來(lái)他的懷疑是正確的, Servlet 的解碼部分完全沒(méi)有考慮雙字節(jié),直接把 %XX 當(dāng)作一個(gè)字符。(原來(lái) Java Soft 也會(huì)犯這幺低級(jí)的錯(cuò)誤!)

          如果你對(duì)這個(gè)問(wèn)題有興趣或者遇到了同樣的煩惱的話,你可以按照他的步驟 對(duì)Servlet.jar 進(jìn)行修改

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

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

          form=HttpUtils.parsePostData(……)

          千萬(wàn)別忘了編譯后放到 Servlet.jar 里面。


          java中文問(wèn)題詳解
          http://www.cn-java.com/target/news.php?news_id=210
          關(guān)于Java中文問(wèn)題的幾條分析原則http://www-900.ibm.com/developerWorks/cn/java/l-javachinese/index.shtml
          全方位解決xml中文問(wèn)題http://www.csdn.net/develop/read_article.asp?id=18901
          servlet 中的漢字編碼問(wèn)題http://www-900.ibm.com/developerWorks/cn/java/jsp_dbcsz/index.shtml
          Java程序的國(guó)際化和本地化介紹http://www-900.ibm.com/developerWorks/cn/java/joy-i18n/index.shtml
          Java 編程技術(shù)中漢字問(wèn)題的分析及解決http://www-900.ibm.com/developerWorks/cn/java/java_chinese/index.shtml
          Unicode專題http://www-900.ibm.com/developerWorks/cn/theme/unicode.shtml
          這里也有篇專家寫的文章:
          http://www.javaworld.com/javaworld/jw-04-2004/jw-0419-multibytes.html

          主站蜘蛛池模板: 会泽县| 津南区| 二连浩特市| 朝阳区| 揭西县| 贡嘎县| 稷山县| 潞城市| 芜湖市| 双城市| 扎兰屯市| 曲周县| 盐山县| 昌吉市| 林芝县| 杭州市| 健康| 平乐县| 湘西| 浏阳市| 满城县| 阳新县| 高陵县| 项城市| 天等县| 涞水县| 永济市| 福建省| 金塔县| 兴和县| 宝应县| 仁布县| 尚义县| 长丰县| 吉安市| 上饶市| 焉耆| 绵阳市| 扶沟县| 日照市| 乌兰浩特市|