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

圖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ù)傳遞過程中沒有丟失,則它就會(huì)正確的運(yùn)行。
這種EJB類和不可以直接運(yùn)行的支持類, 它的轉(zhuǎn)化過程可用圖2更加明確的表示出來:

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

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

圖4
3、分析常見的JAVA中文問題幾個(gè)必須清楚的原則
首先,經(jīng)過上面的詳細(xì)分析,我們可以清晰地看到,任何JAVA程序的生命期中,其編碼轉(zhuǎn)換的關(guān)鍵過程是在于:最初編譯成class文件的轉(zhuǎn)碼和最終向用戶輸出的轉(zhuǎn)碼過程。
其次,我們必須了解JAVA在編譯時(shí)支持的、常用的編碼格式有以下幾種:
*ISO-8859-1,8-bit, 同8859_1,ISO-8859-1,ISO_8859_1等編碼
*Cp1252,美國英語編碼,同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ò)展來的。同時(shí),GBK編碼包含了20902個(gè)漢字,編碼范圍為:0x8140-0xfefe,所有的字符可以一一對(duì)應(yīng)到UNICODE2.0中來。
再次,對(duì)于放在操作系統(tǒng)中的.java源程序文件,在編譯時(shí),我們可以指定它內(nèi)容的編碼格式,具體來說用-encoding來指定。注意:如果源程序中含有中文字符,而你用-encoding指定為其它的編碼字符,顯然是要出錯(cuò)的。用-encoding指定源文件的編碼方式為GBK或gb2312,無論我們?cè)谑裁聪到y(tǒng)上編譯含有中文字符的JAVA源程序都不會(huì)有問題,它都會(huì)正確地將中文轉(zhuǎn)化為UNICODE存儲(chǔ)在class文件中。
然后,我們必須清楚,幾乎所有的WEB容器在其內(nèi)部默認(rèn)的字符編碼格式都是以ISO-8859-1為默認(rèn)值的,同時(shí),幾乎所有的瀏覽器在傳遞參數(shù)時(shí)都是默認(rèn)以UTF-8的方式來傳遞參數(shù)的。所以,雖然我們的Java源文件在出入口的地方指定了正確的編碼方式,但其在容器內(nèi)部運(yùn)行時(shí)還是以ISO-8859-1來處理的。