??
Abstract:本文深入分析了Java程序設(shè)計(jì)中Java編譯器對(duì)java源文件和JVM對(duì)class類文件的編碼/解碼過(guò)程,通過(guò)此過(guò)程的解析透視出了Java編程中中文問(wèn)題產(chǎn)生的根本原因,最后給出了建議的最優(yōu)化的解決Java中文問(wèn)題的方法。?
1、中文問(wèn)題的來(lái)源
??? 計(jì)算機(jī)最初的操作系統(tǒng)支持的編碼是單字節(jié)的字符編碼,于是,在計(jì)算機(jī)中一切處理程序最初都是以單字節(jié)編碼的英文為準(zhǔn)進(jìn)行處理。隨著計(jì)算機(jī)的發(fā)展,為了適應(yīng)世界其它民族的語(yǔ)言(當(dāng)然包括我們的漢字),人們提出了UNICODE編碼,它采用雙字節(jié)編碼,兼容英文字符和其它民族的雙字節(jié)字符編碼,所以,目前,大多數(shù)國(guó)際***的軟件內(nèi)部均采用UNICODE編碼,在軟件運(yùn)行時(shí),它獲得本地支持系統(tǒng)(多數(shù)時(shí)間是操作系統(tǒng))默認(rèn)支持的編碼格式,然后再將軟件內(nèi)部的 UNICODE轉(zhuǎn)化為本地系統(tǒng)默認(rèn)支持的格式顯示出來(lái)。Java的JDK和JVM即是如此,我這里說(shuō)的JDK是指國(guó)際版的JDK,我們大多數(shù)程序員使用的是國(guó)際化的JDK版本,以下所有的JDK均指國(guó)際化的JDK版本。我們的漢字是雙字節(jié)編碼語(yǔ)言,為了能讓計(jì)算機(jī)處理中文,我們自己制定的gb2312、 GBK、GBK2K等標(biāo)準(zhǔn)以適應(yīng)計(jì)算機(jī)處理的需求。所以,大部分的操作系統(tǒng)為了適應(yīng)我們處理中文的需求,均定制有中文操作系統(tǒng),它們采用的是GBK, GB2312編碼格式以正確顯示我們的漢字。如:中文Win2K默認(rèn)采用的是GBK編碼顯示,在中文WIN2k中保存文件時(shí)默認(rèn)采用的保存文件的編碼格式也是GBK的,即,所有在中文WIN2K中保存的文件它的內(nèi)部編碼默認(rèn)均采用GBK編碼,注意:GBK是在GB2312基礎(chǔ)上擴(kuò)充來(lái)的。
??? 由于Java語(yǔ)言內(nèi)部采用UNICODE編碼,所以在JAVA程序運(yùn)行時(shí),就存在著一個(gè)從UNICODE編碼和對(duì)應(yīng)的操作系統(tǒng)及瀏覽器支持的編碼格式轉(zhuǎn)換輸入、輸出的問(wèn)題,這個(gè)轉(zhuǎn)換過(guò)程有著一系列的步驟,如果其中任何一步出錯(cuò),則顯示出來(lái)的漢字就會(huì)出是亂碼,這就是我們常見(jiàn)的JAVA中文問(wèn)題。
??? 同時(shí),Java是一個(gè)跨平臺(tái)的編程語(yǔ)言,也即我們編寫(xiě)的程序不僅能在中文windows上運(yùn)行,也能在中文Linux等系統(tǒng)上運(yùn)行,同時(shí)也要求能在英文等系統(tǒng)上運(yùn)行(我們經(jīng)常看到有人把在中文win2k上編寫(xiě)的JAVA程序,移植到英文Linux上運(yùn)行)。這種移植操作也會(huì)帶來(lái)中文問(wèn)題。
??? 還有,有人使用英文的操作系統(tǒng)和英文的IE等瀏覽器,來(lái)運(yùn)行帶中文字符的程序和瀏覽中文網(wǎng)頁(yè),它們本身就不支持中文,也會(huì)帶來(lái)中文問(wèn)題。
??? 幾乎所有的瀏覽器默認(rèn)在傳遞參數(shù)時(shí)都是以UTF-8編碼格式來(lái)傳遞,而不是按中文編碼傳遞,所以,傳遞中文參數(shù)時(shí)也會(huì)有問(wèn)題,從而帶來(lái)亂碼現(xiàn)象。
??? 總之,以上幾個(gè)方面是JAVA中的中文問(wèn)題的主要來(lái)源,我們把以上原因造成的程序不能正確運(yùn)行而產(chǎn)生的問(wèn)題稱作:JAVA中文問(wèn)題。
2、JAVA編碼轉(zhuǎn)換的詳細(xì)過(guò)程
??? 我們常見(jiàn)的JAVA程序包括以下類別:
???? *直接在console上運(yùn)行的類(包括可視化界面的類)
???? *JSP代碼類(注:JSP是Servlets類的變型)
???? *Servelets類
???? *EJB類
???? *其它不可以直接運(yùn)行的支持類
??? 這些類文件中,都有可能含有中文字符串,并且我們常用前三類JAVA程序和用戶直接交互,用于輸出和輸入字符,如:我們?cè)贘SP和Servlet中得到客戶端送來(lái)的字符,這些字符也包括中文字符。無(wú)論這些JAVA類的作用如何,這些JAVA程序的生命周期都是這樣的:
??? *編程人員在一定的操作系統(tǒng)上選擇一個(gè)合適的編輯軟件來(lái)實(shí)現(xiàn)源程序代碼并以.java擴(kuò)展名保存在操作系統(tǒng)中,例如我們?cè)谥形膚in2k中用記事本編輯一個(gè)java源程序;
???? *編程人員用JDK中的javac.exe來(lái)編譯這些源代碼,形成.class類(JSP文件是由容器調(diào)用JDK來(lái)編譯的);
???? *直接運(yùn)行這些類或?qū)⑦@些類布署到WEB容器中去運(yùn)行,并輸出結(jié)果。
??? 那么,在這些過(guò)程中,JDK和JVM是如何將這些文件如何編碼和解碼并運(yùn)行的呢?
這里,我們以中文win2k操作系統(tǒng)為例說(shuō)明JAVA類是如何來(lái)編碼和被解碼的。
??? 第一步,我們?cè)谥形膚in2k中用編輯軟件如記事本編寫(xiě)一個(gè)Java源程序文件(包括以上五類JAVA 程序),程序文件在保存時(shí)默認(rèn)采用了操作系統(tǒng)默認(rèn)支持GBK編碼格式(操作系統(tǒng)默認(rèn)支持的格式為file.encoding格式)形成了一個(gè).java文件,也即,java程序在被編譯前,我們的JAVA源程序文件是采用操作系統(tǒng)默認(rèn)支持的file.encoding編碼格式保存的,java源程序中含有中文信息字符和英文程序代碼;要查看系統(tǒng)的file.encoding參數(shù),可以用以下代碼:
public class ShowSystemDefaultEncoding {
public static void main(String[] args) {
String encoding = System.getProperty("file.encoding");
System.out.println(encoding);
}}
??? 第二步,我們用JDK的javac.exe文件編譯我們的Java源程序,由于JDK是國(guó)際版的,在編譯的時(shí)候,如果我們沒(méi)有用-encoding參數(shù)指定我們的JAVA源程序的編碼格式,則javac.exe首先獲得我們操作系統(tǒng)默認(rèn)采用的編碼格式,也即在編譯java程序時(shí),若我們不指定源程序文件的編碼格式,JDK首先獲得操作系統(tǒng)的file.encoding參數(shù)(它保存的就是操作系統(tǒng)默認(rèn)的編碼格式,如WIN2k,它的值為GBK),然后JDK就把我們的java源程序從file.encoding編碼格式轉(zhuǎn)化為JAVA內(nèi)部默認(rèn)的 UNICODE格式放入內(nèi)存中。然后,javac把轉(zhuǎn)換后的unicode格式的文件進(jìn)行編譯成.class類文件,此時(shí).class文件是 UNICODE編碼的,它暫放在內(nèi)存中,緊接著,JDK將此以UNICODE編碼的編譯后的class文件保存到我們的操作系統(tǒng)中形成我們見(jiàn)到的. class文件。對(duì)我們來(lái)說(shuō),我們最終獲得的.class文件是內(nèi)容以UNICODE編碼格式保存的類文件,它內(nèi)部包含我們?cè)闯绦蛑械闹形淖址徊贿^(guò)此時(shí)它己經(jīng)由file.encoding格式轉(zhuǎn)化為UNICODE格式了。
??? 這一步中,對(duì)于JSP源程序文件是不同的,對(duì)于JSP,這個(gè)過(guò)程是這樣的:即WEB容器調(diào)用JSP編譯器,JSP編譯器先查看JSP文件中是否設(shè)置有文件編碼格式,如果JSP文件中沒(méi)有設(shè)置JSP文件的編碼格式,則JSP編譯器調(diào)用JDK先把JSP文件用JVM默認(rèn)的字符編碼格式(也即WEB容器所在的操作系統(tǒng)的默認(rèn)的file.encoding)轉(zhuǎn)化為臨時(shí)的Servlet類,然后再把它編譯成UNICODE格式的class類,并保存在臨時(shí)文件夾中。如:在中文win2k上,WEB容器就把JSP文件從GBK編碼格式轉(zhuǎn)化為UNICODE格式,然后編譯成臨時(shí)保存的Servlet類,以響應(yīng)用戶的請(qǐng)求。
??? 第三步,運(yùn)行第二步編譯出來(lái)的類,分為三種情況:
??? A、 直接在console上運(yùn)行的類
??? B、 EJB類和不可以直接運(yùn)行的支持類(如JavaBean類)
??? C、 JSP代碼和Servlet類
??? D、 JAVA程序和數(shù)據(jù)庫(kù)之間
??? 下面我們分這四種情況來(lái)看。
??? A、直接在console上運(yùn)行的類
??? 這種情況,運(yùn)行該類首先需要JVM支持,即操作系統(tǒng)中必須安裝有JRE。運(yùn)行過(guò)程是這樣的:首先java啟動(dòng)JVM,此時(shí)JVM讀出操作系統(tǒng)中保存的 class文件并把內(nèi)容讀入內(nèi)存中,此時(shí)內(nèi)存中為UNICODE格式的class類,然后JVM運(yùn)行它,如果此時(shí)此類需要接收用戶輸入,則類會(huì)默認(rèn)用 file.encoding編碼格式對(duì)用戶輸入的串進(jìn)行編碼并轉(zhuǎn)化為unicode保存入內(nèi)存(用戶可以設(shè)置輸入流的編碼格式)。程序運(yùn)行后,產(chǎn)生的字符串(UNICODE編碼的)再回交給JVM,最后JRE把此字符串再轉(zhuǎn)化為file.encoding格式(用戶可以設(shè)置輸出流的編碼格式)傳遞給操作系統(tǒng)顯示接口并輸出到界面上。
??? 對(duì)于這種直接在console上運(yùn)行的類,它的轉(zhuǎn)化過(guò)程可用圖1更加明確的表示出來(lái):
圖1

以上每一步的轉(zhuǎn)化都需要正確的編碼格式轉(zhuǎn)化,才能最終不出現(xiàn)亂碼現(xiàn)象。
??? B、EJB類和不可以直接運(yùn)行的支持類(如JavaBean類)
??? 由于EJB類和不可以直接運(yùn)行的支持類,它們一般不與用戶直接交互輸入和輸出,它們常常與其它的類進(jìn)行交互輸入和輸出,所以它們?cè)诘诙奖痪幾g后,就形成了內(nèi)容是UNICODE編碼的類保存在操作系統(tǒng)中了,以后只要它與其它的類之間的交互在參數(shù)傳遞過(guò)程中沒(méi)有丟失,則它就會(huì)正確的運(yùn)行。
這種EJB類和不可以直接運(yùn)行的支持類, 它的轉(zhuǎn)化過(guò)程可用圖2更加明確的表示出來(lái):
圖2

??? C、JSP代碼和Servlet類
??? 經(jīng)過(guò)第二步后,JSP文件也被轉(zhuǎn)化為Servlets類文件,只不過(guò)它不像標(biāo)準(zhǔn)的Servlets一校存在于classes目錄中,它存在于WEB容器的臨時(shí)目錄中,故這一步中我們也把它做為Servlets來(lái)看。
??? 對(duì)于Servlets,客戶端請(qǐng)求它時(shí),WEB容器調(diào)用它的JVM來(lái)運(yùn)行Servlet,首先,JVM把Servlet的class類從系統(tǒng)中讀出并裝入內(nèi)存中,內(nèi)存中是以UNICODE編碼的Servlet類的代碼,然后JVM在內(nèi)存中運(yùn)行該Servlet類,如果Servlet在運(yùn)行的過(guò)程中,需要接受從客戶端傳來(lái)的字符如:表單輸入的值和URL中傳入的值,此時(shí)如果程序中沒(méi)有設(shè)定接受參數(shù)時(shí)采用的編碼格式,則WEB容器會(huì)默認(rèn)采用ISO-8859- 1編碼格式來(lái)接受傳入的值并在JVM中轉(zhuǎn)化為UNICODE格式的保存在WEB容器的內(nèi)存中。Servlet運(yùn)行后生成輸出,輸出的字符串是 UNICODE格式的,緊接著,容器將Servlet運(yùn)行產(chǎn)生的UNICODE格式的串(如html語(yǔ)法,用戶輸出的串等)直接發(fā)送到客戶端瀏覽器上并輸出給用戶,如果此時(shí)指定了發(fā)送時(shí)輸出的編碼格式,則按指定的編碼格式輸出到瀏覽器上,如果沒(méi)有指定,則默認(rèn)按ISO-8859-1編碼發(fā)送到客戶的瀏覽器上。這種JSP代碼和Servlet類,它的轉(zhuǎn)化過(guò)程可用圖3更加明確地表示出來(lái):
圖3

D、Java程序和數(shù)據(jù)庫(kù)之間
??? 對(duì)于幾乎所有數(shù)據(jù)庫(kù)的JDBC驅(qū)動(dòng)程序,默認(rèn)的在JAVA程序和數(shù)據(jù)庫(kù)之間傳遞數(shù)據(jù)都是以ISO-8859-1為默認(rèn)編碼格式的,所以,我們的程序在向數(shù)據(jù)庫(kù)內(nèi)存儲(chǔ)包含中文的數(shù)據(jù)時(shí),JDBC首先是把程序內(nèi)部的UNICODE編碼格式的數(shù)據(jù)轉(zhuǎn)化為ISO-8859-1的格式,然后傳遞到數(shù)據(jù)庫(kù)中,在數(shù)據(jù)庫(kù)保存數(shù)據(jù)時(shí),它默認(rèn)即以ISO-8859-1保存,所以,這是為什么我們常常在數(shù)據(jù)庫(kù)中讀出的中文數(shù)據(jù)是亂碼。
??? 對(duì)于JAVA程序和數(shù)據(jù)庫(kù)之間的數(shù)據(jù)傳遞,我們可以用圖4清晰地表示出來(lái)
圖4

??? 3、分析常見(jiàn)的JAVA中文問(wèn)題幾個(gè)必須清楚的原則
??? 首先,經(jīng)過(guò)上面的詳細(xì)分析,我們可以清晰地看到,任何JAVA程序的生命期中,其編碼轉(zhuǎn)換的關(guān)鍵過(guò)程是在于:最初編譯成class文件的轉(zhuǎn)碼和最終向用戶輸出的轉(zhuǎn)碼過(guò)程。
??? 其次,我們必須了解JAVA在編譯時(shí)支持的、常用的編碼格式有以下幾種:
??? *ISO-8859-1,8-bit, 同8859_1,ISO-8859-1,ISO_8859_1等編碼
??? *Cp1252,美國(guó)英語(yǔ)編碼,同ANSI標(biāo)準(zhǔn)編碼
??? *UTF-8,同unicode編碼
??? *GB2312,同gb2312-80,gb2312-1980等編碼
??? *GBK , 同MS936,它是gb2312的擴(kuò)充
??? 及其它的編碼,如韓文、日文、繁體中文等。同時(shí),我們要注意這些編碼間的兼容關(guān)體系如下:
??? unicode和UTF-8編碼是一一對(duì)應(yīng)的關(guān)系。GB2312可以認(rèn)為是GBK的子集,即GBK編碼是在gb2312上擴(kuò)展來(lái)的。同時(shí),GBK編碼包含了20902個(gè)漢字,編碼范圍為:0x8140-0xfefe,所有的字符可以一一對(duì)應(yīng)到UNICODE2.0中來(lái)。
??? 再次,對(duì)于放在操作系統(tǒng)中的.java源程序文件,在編譯時(shí),我們可以指定它內(nèi)容的編碼格式,具體來(lái)說(shuō)用-encoding來(lái)指定。注意:如果源程序中含有中文字符,而你用-encoding指定為其它的編碼字符,顯然是要出錯(cuò)的。用-encoding指定源文件的編碼方式為GBK或gb2312,無(wú)論我們?cè)谑裁聪到y(tǒng)上編譯含有中文字符的JAVA源程序都不會(huì)有問(wèn)題,它都會(huì)正確地將中文轉(zhuǎn)化為UNICODE存儲(chǔ)在class文件中。
????
??? 然后,我們必須清楚,幾乎所有的WEB容器在其內(nèi)部默認(rèn)的字符編碼格式都是以ISO-8859-1為默認(rèn)值的,同時(shí),幾乎所有的瀏覽器在傳遞參數(shù)時(shí)都是默認(rèn)以UTF-8的方式來(lái)傳遞參數(shù)的。所以,雖然我們的Java源文件在出入口的地方指定了正確的編碼方式,但其在容器內(nèi)部運(yùn)行時(shí)還是以ISO-8859- 1來(lái)處理的。
?
4、中文問(wèn)題的分類及其建議最優(yōu)解決辦法??? 了解以上JAVA處理文件的原理之后,我們就可以提出了一套建議最優(yōu)的解決漢字問(wèn)題的辦法。
??? 我們的目標(biāo)是:我們?cè)谥形南到y(tǒng)中編輯的含有中文字符串或進(jìn)行中文處理的JAVA源程序經(jīng)編譯后可以移值到任何其它的操作系統(tǒng)中正確運(yùn)行,或拿到其它操作系統(tǒng)中編譯后能正確運(yùn)行,能正確地傳遞中文和英文參數(shù),能正確地和數(shù)據(jù)庫(kù)交流中英文字符串。
??? 我們的具體思路是:在JAVA程序轉(zhuǎn)碼的入口和出口及JAVA程序同用戶有輸入輸出轉(zhuǎn)換的地方限制編碼方法使之正確即可。
??? 具體解決辦法如下:
??? 1、 針對(duì)直接在console上運(yùn)行的類
??? 對(duì)于這種情況,我們建議在程序編寫(xiě)時(shí),如果需要從用戶端接收用戶的可能含有中文的輸入或含有中文的輸出,程序中應(yīng)該采用字符流來(lái)處理輸入和輸出,具體來(lái)說(shuō),應(yīng)用以下面向字符型節(jié)點(diǎn)流類型:
??? 對(duì)文件:FileReader,F(xiàn)ileWrieter
??????? 其字節(jié)型節(jié)點(diǎn)流類型為:FileInputStream,F(xiàn)ileOutputStream
??? 對(duì)內(nèi)存(數(shù)組):CharArrayReader,CharArrayWriter
??????? 其字節(jié)型節(jié)點(diǎn)流類型為:ByteArrayInputStream,ByteArrayOutputStream
??? 對(duì)內(nèi)存(字符串):StringReader,StringWriter
??? 對(duì)管道:PipedReader,PipedWriter
??????? 其字節(jié)型節(jié)點(diǎn)流類型為:PipedInputStream,PipedOutputStream
??? 同時(shí),應(yīng)該用以下面向字符型處理流來(lái)處理輸入和輸出:
??? BufferedWriter,BufferedReader
??????? 其字節(jié)型的處理流為:BufferedInputeStream,BufferedOutputStream
??? InputStreamReader,OutputStreamWriter
??? 其字節(jié)型的處理流為:DataInputStream,DataOutputStream
??? 其中InputStreamReader和InputStreamWriter用于將字節(jié)流按照指定的字符編碼集轉(zhuǎn)換到字符流,如:
??? InputStreamReader in = new InputStreamReader(System.in,"GB2312");
??? OutputStreamWriter out = new OutputStreamWriter (System.out,"GB2312");
??? 例如:采用如下的示例JAVA編碼就達(dá)到了要求:
??? //Read.java
??? import java.io.*;
??? public class Read {
??? public static void main(String[] args) throws IOException {
??? String str = "\n中文測(cè)試,這是內(nèi)部硬編碼的串"+"\ntest english character";
??? String strin= "";
??? BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in,"gb2312")); //設(shè)置輸入接口按中文編碼
??? BufferedWriter stdout = new BufferedWriter(new OutputStreamWriter(System.out,"gb2312")); //設(shè)置輸出接口按中文編碼
??? stdout.write("請(qǐng)輸入:");
??? stdout.flush();
??? strin = stdin.readLine();
??? stdout.write("這是從用戶輸入的串:"+strin);
??? stdout.write(str);
??? stdout.flush();
??? }}
??? 同時(shí),在編譯程序時(shí),我們用以下方式來(lái)進(jìn)行:
??? javac -encoding gb2312 Read.java
??? 其運(yùn)行結(jié)果如圖5所示:

??? 圖5
2、 針對(duì)EJB類和不可以直接運(yùn)行的支持類(如JavaBean類)
??? 由于這種類它們本身被其它的類調(diào)用,不直接與用戶交互,故對(duì)這種類來(lái)說(shuō),我們的建議的處理方式是內(nèi)部程序中應(yīng)該采用字符流來(lái)處理程序內(nèi)部的中文字符串(具體如上面一節(jié)中一樣),同時(shí),在編譯類時(shí)用-encoding gb2312參數(shù)指示源文件是中文格式編碼的即可。
??? 3、 針對(duì)Servlet類
??? 針對(duì)Servlet,我們建議用以下方法:(我建議將.java文件設(shè)置為UTF8編碼的。當(dāng)然如果是用Eclipse的話,只要設(shè)置下就是了。對(duì)于數(shù)據(jù)庫(kù),我以為編碼最好設(shè)置為UTF8,便于國(guó)際化?。盡可能的使用UTF8碼,? 千里草)
??? 在編譯Servlet類的源程序時(shí),用-encoding指定編碼為GBK或GB2312,且在向用戶輸出時(shí)的編碼部分用response對(duì)象的 setContentType("text/html;charset=GBK");或gb2312來(lái)設(shè)置輸出編碼格式,同樣在接收用戶輸入時(shí),我們用 request.setCharacterEncoding("GB2312");這樣無(wú)論我們的servlet類移植到什么操作系統(tǒng)中,只有客戶端的瀏覽器支持中文顯示,就可以正確顯示。如下是一個(gè)正確的示例:
??? //HelloWorld.java
??? package hello;
??? import java.io.*;
??? import javax.servlet.*;
??? import javax.servlet.http.*;
??? public class HelloWorld extends HttpServlet
??? {
??? public void init() throws ServletException { }
??? public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
??? {
??? request.setCharacterEncoding("GB2312"); //設(shè)置輸入編碼格式
??? response.setContentType("text/html;charset=GB2312"); //設(shè)置輸出編碼格式
??? PrintWriter out = response.getWriter(); //建議使用PrintWriter輸出
??? out.println("<hr>");
??? out.println("Hello World! This is created by Servlet!測(cè)試中文!");
??? out.println("<hr>");
??? }
??? public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
??? {
??? request.setCharacterEncoding("GB2312"); //設(shè)置輸入編碼格式
??? response.setContentType("text/html;charset=GB2312"); //設(shè)置輸出編碼格式
??? String name = request.getParameter("name");
??? String id = request.getParameter("id");
??? if(name==null) name="";
??? if(id==null) id="";
??? PrintWriter out = response.getWriter(); //建議使用PrintWriter輸出
??? out.println("<hr>");
??? out.println("你傳入的中文字串是:" + name);
??? out.println("<hr>你輸入的id是:" + id);
??? out.println("<hr>");
??? }
??? public void destroy() { }
??? }
??????? 請(qǐng)用javac -encoding gb2312 HelloWorld.java來(lái)編譯此程序。
??????? 測(cè)試此Servlet的程序如下所示:
??? <%@page contentType="text/html; charset=gb2312"%>
??? <%request.setCharacterEncoding("GB2312");%>
??? <html><head><title></title>
??? <Script language="JavaScript">
??? function Submit() {
??? //通過(guò)URL傳遞中文字符串值給Servlet
??? document.base.action = "./HelloWorld?name=中文";
??? document.base.method = "POST";
??? document.base.submit();
??? }
??? </Script>
??? </head>
<body bgcolor="#FFFFFF" text="#000000" topmargin="5">
??? <form name="base" method = "POST" target="_self">
??? <input name="id" type="text" value="" size="30">
??? <a href = "JavaScript:Submit()">傳給Servlet</a>
??? </form></body></html>
??? 其運(yùn)行結(jié)果如圖6所示:

??? 圖6
??? 4、 JAVA程序和數(shù)據(jù)庫(kù)之間
??? 為避免JAVA程序和數(shù)據(jù)庫(kù)之間數(shù)據(jù)傳遞出現(xiàn)亂碼現(xiàn)象,我們建議采用以下最優(yōu)方法來(lái)處理:
??? 1、 對(duì)于JAVA程序的處理方法按我們指定的方法處理。
??? 2、 把數(shù)據(jù)庫(kù)默認(rèn)支持的編碼格式改為GBK或GB2312的。
??? 如:在mysql中,我們可以在配置文件my.ini中加入以下語(yǔ)句實(shí)現(xiàn):
??? 在[mysqld]區(qū)增加:
??? default-character-set=gbk
??? 并增加:
??? [client]
??? default-character-set=gbk
??? 在SQL Server2K中,我們可以將數(shù)據(jù)庫(kù)默認(rèn)的語(yǔ)言設(shè)置為Simplified Chinese來(lái)達(dá)到目的。
??? 5、 針對(duì)JSP代碼
??? 由于JSP是在運(yùn)行時(shí),由WEB容器進(jìn)行動(dòng)態(tài)編譯的,如果我們沒(méi)有指定JSP源文件的編碼格式,則JSP編譯器會(huì)獲得服務(wù)器操作系統(tǒng)的 file.encoding值來(lái)對(duì)JSP文件編譯的,它在移植時(shí)最容易出問(wèn)題,如在中文win2k中可以很好運(yùn)行的jsp文件拿到英文linux中就不行,盡管客戶端都是一樣的,那是因?yàn)槿萜髟诰幾gJSP文件時(shí)獲取的操作系統(tǒng)的編碼不同造成的(在中文wink中的file.encoding和在英文 Linux中file.encoding是不同的,且英文Linux的file.encoding對(duì)中文不支持,所以編譯出來(lái)的JSP類就會(huì)有問(wèn)題)。網(wǎng)絡(luò)上討論的大多數(shù)是此類問(wèn)題,多是因?yàn)镴SP文件移植平臺(tái)時(shí)不能正確顯示的問(wèn)題,對(duì)于這類問(wèn)題,我們了解了JAVA中程序編碼轉(zhuǎn)換的原理,解決起來(lái)就容易多了。我們建議的解決辦法如下:
??? 1、我們要保證JSP向客戶端輸出時(shí)是采用中文編碼方式輸出的,即無(wú)論如何我們首先在我們的JSP源代編中加入以下一行:
??? <%@page contentType="text/html; charset=gb2312"%>
??? 2、為了讓JSP能正確獲得傳入的參數(shù),我們?cè)贘SP源文件頭加入下面一句:
??? <%request.setCharacterEncoding("GB2312");%>
??? 3、為了讓JSP編譯器能正確地解碼我們的含有中文字符的JSP文件,我們需要在JSP源文件中指定我們的JSP源文件的編碼格式,具體來(lái)說(shuō),我們?cè)贘SP源文件頭上加入下面的一句即可:
??? <%@page pageEncoding="GB2312"%>或<%@page pageEncoding="GBK"%>
??? 這是JSP規(guī)范2.0新增加的指令。
??? 我們建議使用此方法來(lái)解JSP文件中的中文問(wèn)題,下面的代碼是一個(gè)正確做法的JSP文件的測(cè)試程序:
//testchinese.jsp
??? <%@page pageEncoding="GB2312"%>
??? <%@page contentType="text/html; charset=gb2312"%>
??? <%request.setCharacterEncoding("GB2312");%>
??? <%
??? String action = request.getParameter("ACTION");
??? String name = "";
??? String str = "";
??? if(action!=null && action.equals("SENT"))
??? {
??? name = request.getParameter("name");
??? str = request.getParameter("str");
??? }
??? %>
??? <html>
??? <head>
??? <title></title>
??? <Script language="JavaScript">
??? function Submit()
??? {
??? document.base.action = "?ACTION=SENT&str=傳入的中文";
??? document.base.method = "POST";
??? document.base.submit();
??? }
??? </Script>
??? </head>
??? <body bgcolor="#FFFFFF" text="#000000" topmargin="5">
??? <form name="base" method = "POST" target="_self">
??? <input type="text" name="name" value="" size="30">
??? <a href = "JavaScript:Submit()">提交</a>
??? </form>
??? <%
??? if(action!=null && action.equals("SENT"))
??? {
??? out.println("<br>你輸入的字符為:"+name);
??? out.println("<br>你通過(guò)URL傳入的字符為:"+str);
??? }
??? %>
??? </body>
??? </html>
??? 如圖7是此程序運(yùn)行的結(jié)果示意圖:

??? 圖7
??? 5、總結(jié)
??? 在上面的詳細(xì)分析中,我們清晰地給出了JAVA在處理源程序過(guò)程中的詳細(xì)轉(zhuǎn)換過(guò)程,為我們正確解決JAVA編程中的中文問(wèn)題提供了基礎(chǔ)。同時(shí),我們給出了認(rèn)為是最優(yōu)的解決JAVA中文問(wèn)題的辦法。
我的補(bǔ)充(需要特別注意): 在表單(form )提交時(shí),如果提交的方法為get,那么request.setCharacterEncoding() 是不起作用的。此時(shí)在服務(wù)器端得到的字符編碼仍然是ISO8859-1,而不是你設(shè)置的編碼。所以一般我們?cè)谔峤粩?shù)據(jù)時(shí),盡量使用post方法,此時(shí) request.setCharacterEncoding()方法起效。
?如果非要使用get方法傳form則要轉(zhuǎn)換一下才行:?
? ----- ?
? <%@ ? page ? contentType="text/html;charset=gb2312"%> ?
? <%! ?
? ? ? ? ? public ? String ? getStr(String ? str){ ?
? try{ ?
? String ? temp_p=str; ?
? byte[] ? temp_t=temp_p.getBytes("ISO8859-1"); ?
? String ? temp=new ? String(temp_t); ?
? return ? temp; ?
? } ?
? catch(Exception ? e){ ?
? } ?
? return ? "null"; ?
? ? } ?
? ? %> ?
? 然后把String ? userId=request.getParameter("userId");改成 ?
? String ? userId=getStr(request.getParameter("userId"));??
--------------------------------------------
我來(lái)說(shuō)一下tomcat如何實(shí)現(xiàn)JSP的你就明白了。
預(yù)備知識(shí):
1.字節(jié)和unicode
Java內(nèi)核是unicode的,就連class文件也是,但是很多媒體,包括文件/流的保存方式
是使用字節(jié)流的。 因此Java要對(duì)這些字節(jié)流經(jīng)行轉(zhuǎn)化。char是unicode的,而byte是字節(jié).
Java中byte/char互轉(zhuǎn)的函數(shù)在sun.io的包中間有。其中ByteToCharConverter類是中調(diào)度,
可以用來(lái)告訴你,你用的Convertor。其中兩個(gè)很常用的靜態(tài)函數(shù)是
public static ByteToCharConverter getDefault() ;
public static ByteToCharConverter getConverter(String encoding);
如果你不指定converter,則系統(tǒng)會(huì)自動(dòng)使用當(dāng)前的Encoding,GB平臺(tái)上用GBK,EN平臺(tái)上用
8859_1
我們來(lái)就一個(gè)簡(jiǎn)單的例子:
"你"的gb碼是:0xC4E3 ,unicode是0x4F60
你用:
--encoding="gb2312";
--byte b[]={(byte)'\u00c4',(byte)'\u00E3'};
--convertor=ByteToCharConverter.getConverter(encoding);
--char [] c=converter.convertAll(b);
--for(int i=0;i<c.length;c++)
--{
-- System.out.println(Integer.toHexString(c[i]));
--}
--打印出來(lái)是0x4F60
--但是如果使用8859_1的編碼,打印出來(lái)是
--0x00C4,0x00E3
----例1
反過(guò)來(lái):
? --encoding="gb2312";
? --char c[]={'\u4F60'};
--convertor=ByteToCharConverter.getConverter(encoding);
--byte [] b=converter.convertAll(c);
--for(int i=0;i<b.length;c++)
--{
-- System.out.println(Integer.toHexString(b[i]));
--}
--打印出來(lái)是:0xC4,0xE3
----例2
--如果用8859_1就是0x3F,?號(hào),表示無(wú)法轉(zhuǎn)化 --
很多中文問(wèn)題就是從這兩個(gè)最簡(jiǎn)單的類派生出來(lái)的。而卻有很多類
不直接支持把Encoding輸入,這給我們帶來(lái)諸多不便。很多程序難得用encoding
了,直接用default的encoding,這就給我們移植帶來(lái)了很多困難
--
2.UTF-8
--UTF-8是和Unicode一一對(duì)應(yīng)的,其實(shí)現(xiàn)很簡(jiǎn)單
--
-- 7位的Unicode: 0 _ _ _ _ _ _ _
--11位的Unicode: 1 1 0 _ _ _ _ _ 1 0 _ _ _ _ _ _
--16位的Unicode: 1 1 1 0 _ _ _ _ 1 0 _ _ _ _ _ _ 1 0 _ _ _ _ _ _
--21位的Unicode: 1 1 1 1 0 _ _ _ 1 0 _ _ _ _ _ _ 1 0 _ _ _ _ _ _ 1 0 _ _ _ _ _ _
--大多數(shù)情況是只使用到16位以下的Unicode:
--"你"的gb碼是:0xC4E3 ,unicode是0x4F60
--我們還是用上面的例子
-- --例1:0xC4E3的二進(jìn)制:
-- -- 1 1 0 0 0 1 0 0 1 1 1 0 0 0 1 1
-- -- 由于只有兩位我們按照兩位的編碼來(lái)排,但是我們發(fā)現(xiàn)這行不通,
-- -- 因?yàn)榈冢肺徊皇?因此,返回"?"
-- --
-- --例2:0x4F60的二進(jìn)制:
-- -- 0 1 0 0 1 1 1 1 0 1 1 0 0 0 0 0
-- -- 我們用UTF-8補(bǔ)齊,變成:
-- -- 11100100 10111101 10100000
-- -- E4--BD-- A0
-- -- 于是返回0xE4,0xBD,0xA0
-- --
3.String和byte[]
--String其實(shí)核心是char[],然而要把byte轉(zhuǎn)化成String,必須經(jīng)過(guò)編碼。
--String.length()其實(shí)就是char數(shù)組的長(zhǎng)度,如果使用不同的編碼,很可
--能會(huì)錯(cuò)分,造成散字和亂碼。
--例:
----byte [] b={(byte)'\u00c4',(byte)'\u00e3'};
----String str=new String(b,encoding); ----
----如果encoding=8859_1,會(huì)有兩個(gè)字,但是encoding=gb2312只有一個(gè)字 ----
--這個(gè)問(wèn)題在處理分頁(yè)是經(jīng)常發(fā)生
4.Reader,Writer/InputStream,OutputStream
--Reader和Writer核心是char,InputStream和OutputStream核心是byte。
--但是Reader和Writer的主要目的是要把Char讀/寫(xiě)InputStream/OutputStream
--一個(gè)reader的例子:
--文件test.txt只有一個(gè)"你"字,0xC4,0xE3--
--String encoding=;
--InputStreamReader reader=new InputStreamReader(
----new FileInputStream("text.txt"),encoding);
--char []c=new char[10];
--int length=reader.read(c);
--for(int i=0;i<c.length;i++)
----System.out.println(c[i]);
--如果encoding是gb2312,則只有一個(gè)字符,如果encoding=8859_1,則有兩個(gè)字符
--------
--
--
----
2.我們要對(duì)Java的編譯器有所了解:
--javac -encoding
我們常常沒(méi)有用到ENCODING這個(gè)參數(shù)。其實(shí)Encoding這個(gè)參數(shù)對(duì)于跨平臺(tái)的操作是很重要的。
如果沒(méi)有指定Encoding,則按照系統(tǒng)的默認(rèn)Encoding,gb平臺(tái)上是gb2312,英文平臺(tái)上是ISO8859_1。
--Java的編譯器實(shí)際上是調(diào)用sun.tools.javac.Main的類,對(duì)文件進(jìn)行編譯,這個(gè)類 --
有compile函數(shù)中間有一個(gè)encoding的變量,-encoding的參數(shù)其實(shí)直接傳給encoding變量。
編譯器就是根據(jù)這個(gè)變量來(lái)讀取java文件的,然后把用UTF-8形式編譯成class文件。
一個(gè)例子:
--public void test()
--{
----String str="你";
----FileWriter write=new FileWriter("test.txt");
----write.write(str);
----write.close();
--}
----例3
--如果用gb2312編譯,你會(huì)找到E4 BD A0的字段
--
--如果用8859_1編譯,
--00C4 00E3的二進(jìn)制:
--00000000 11000100 00000000 11100011--
--因?yàn)槊總€(gè)字符都大于7位,因此用11位編碼:
--11000001 10000100 11000011 10100011
--C1-- 84-- C3-- A3
--你會(huì)找到C1 84 C3 A3 --
但是我們往往忽略掉這個(gè)參數(shù),因此這樣往往會(huì)有跨平臺(tái)的問(wèn)題:
-- 例3在中文平臺(tái)上編譯,生成ZhClass
-- 例3在英文平臺(tái)上編譯,輸出EnClass
--1. ZhClass在中文平臺(tái)上執(zhí)行OK,但是在英文平臺(tái)上不行
--2. EnClass在英文平臺(tái)上執(zhí)行OK,但是在中文平臺(tái)上不行
原因:
--1.在中文平臺(tái)上編譯后,其實(shí)str在運(yùn)行態(tài)的char[]是0x4F60, ----
--在中文平臺(tái)上運(yùn)行,F(xiàn)ileWriter的缺省編碼是gb2312,因此
--CharToByteConverter會(huì)自動(dòng)用調(diào)用gb2312的converter,把str轉(zhuǎn)化
--成byte輸入到FileOutputStream中,于是0xC4,0xE3放進(jìn)了文件。
--但是如果是在英文平臺(tái)下,CharToByteConverter的缺省值是8859_1,
--FileWriter會(huì)自動(dòng)調(diào)用8859_1去轉(zhuǎn)化str,但是他無(wú)法解釋,因此他會(huì)
--輸出"?" ----
--2. 在英文平臺(tái)上編譯后,其實(shí)str在運(yùn)行態(tài)的char[]是0x00C4 0x00E3, ----
--在中文平臺(tái)上運(yùn)行,中文無(wú)法識(shí)別,因此會(huì)出現(xiàn)??
-- 在英文平臺(tái)上,0x00C4-->0xC4,0x00E3->0xE3,因此0xC4,0xE3被放進(jìn)了
--文件
----
1.對(duì)于JSP正文的解釋:
--Tomcat首先看一下你的葉面中有沒(méi)有"<
%@page include的符號(hào)。有,則在相同
--地方設(shè)定response.setContentType(..);按照encoding的來(lái)讀,沒(méi)有他按照8859_1
--讀取文件,然后用UTF-8寫(xiě)成.java文件,然后用sun.tools.Main去讀取這個(gè)文件,
--(當(dāng)然它使用UTF-8去讀),然后編譯成class文件
--setContentType改變的是out的屬性,out變量缺省的encoding是8859_1
2.對(duì)Parameter的解釋
--很不幸Parameter只有ISO8859_1的解釋,這個(gè)質(zhì)料可以在servlet的實(shí)現(xiàn)代碼中找到。
3.對(duì)include的解釋
格式的,但是很不幸,由于那個(gè)寫(xiě)"org.apache.jasper.compiler.Parser"的人
在數(shù)組JspUtil.ValidAttribute[]忘記加了一個(gè)參數(shù):encoding,因此導(dǎo)致不支
持這種方式。你完全可以編譯源代碼,加上對(duì)encoding的支持
總結(jié):
如果你在NT底下,最簡(jiǎn)單的方法就是欺騙java,不加任何Encoding變量:
<html>
你好<%=request.getParameter("value")%>
</html>
http://localhost/test/test.jsp?value=你
結(jié)果:你好你
但這種方法局限性較大,比如對(duì)上傳的文章分段,這樣的做法是死定的,最好的
解決方案是用這種方案:
<%@ page contentType="text/html;charset=gb2312" %>
<html>
你好<%=new String(request.getParameter("value").getBytes("8859_1"),"gb2312")%>
</html>