深入剖析Java編程中的中文問題及建議最優解決方法--上篇 - [原創]

說明:本文爲作者原創,作者聯繫地址爲:[email protected]。由於Java編程中的中文問題是一個老生常談的問題,在閱讀了許多關於 Java中文問題解決方法之後,結合作者的編程實踐,我發現過去談的許多方法都不能清晰地說明問題及解決問題,尤其是跨平臺時的中文問題。於是我給出此篇文章,內容包括對控制檯運行的class、Servelets、JSP及EJB類中的中文問題我剖析和建議解決辦法。希望大家指教。任何引用本文請註明出處!!
Abstract:本文深入分析了Java程序設計中Java編譯器對java源文件和JVM對class類文件的編碼/解碼過程,通過此過程的解析透視出了Java編程中中文問題產生的根本原因,最後給出了建議的最優化的解決Java中文問題的方法。
1、中文問題的來源
     計算機最初的操作系統支持的編碼是單字節的字符編碼,於是,在計算機中一切處理程序最初都是以單字節編碼的英文爲準進行處理。隨着計算機的發展,爲了適應世界其它民族的語言(當然包括我們的漢字),人們提出了UNICODE編碼,它採用雙字節編碼,兼容英文字符和其它民族的雙字節字符編碼,所以,目前,大多數國際性的軟件內部均採用UNICODE編碼,在軟件運行時,它獲得本地支持系統(多數時間是操作系統)默認支持的編碼格式,然後再將軟件內部的 UNICODE轉化爲本地系統默認支持的格式顯示出來。Java的JDK和JVM即是如此,我這裏說的JDK是指國際版的JDK,我們大多數程序員使用的是國際化的JDK版本,以下所有的JDK均指國際化的JDK版本。我們的漢字是雙字節編碼語言,爲了能讓計算機處理中文,我們自己制定的gb2312、 GBK、GBK2K等標準以適應計算機處理的需求。所以,大部分的操作系統爲了適應我們處理中文的需求,均定製有中文操作系統,它們採用的是GBK, GB2312編碼格式以正確顯示我們的漢字。如:中文Win2K默認採用的是GBK編碼顯示,在中文WIN2k中保存文件時默認採用的保存文件的編碼格式也是GBK的,即,所有在中文WIN2K中保存的文件它的內部編碼默認均採用GBK編碼,注意:GBK是在GB2312基礎上擴充來的。
     由於Java語言內部採用UNICODE編碼,所以在JAVA程序運行時,就存在着一個從UNICODE編碼和對應的操作系統及瀏覽器支持的編碼格式轉換輸入、輸出的問題,這個轉換過程有着一系列的步驟,如果其中任何一步出錯,則顯示出來的漢字就會出是亂碼,這就是我們常見的JAVA中文問題。
     同時,Java是一個跨平臺的編程語言,也即我們編寫的程序不僅能在中文windows上運行,也能在中文Linux等系統上運行,同時也要求能在英文等系統上運行(我們經常看到有人把在中文win2k上編寫的JAVA程序,移植到英文Linux上運行)。這種移植操作也會帶來中文問題。
    還有,有人使用英文的操作系統和英文的IE等瀏覽器,來運行帶中文字符的程序和瀏覽中文網頁,它們本身就不支持中文,也會帶來中文問題。
    有,幾乎所有的瀏覽器默認在傳遞參數時都是以UTF-8編碼格式來傳遞,而不是按中文編碼傳遞,所以,傳遞中文參數時也會有問題,從而帶來亂碼現象。
    總之,以上幾個方面是JAVA中的中文問題的主要來源,我們把以上原因造成的程序不能正確運行而產生的問題稱作:JAVA中文問題。
2、JAVA編碼轉換的詳細過程
    我們常見的JAVA程序包括以下類別:
     *直接在console上運行的類(包括可視化界面的類)
     *JSP代碼類(注:JSP是Servlets類的變型)
     *Servelets類
     *EJB類
     *其它不可以直接運行的支持類
     這些類文件中,都有可能含有中文字符串,並且我們常用前三類JAVA程序和用戶直接交互,用於輸出和輸入字符,如:我們在JSP和Servlet中得到客戶端送來的字符,這些字符也包括中文字符。無論這些JAVA類的作用如何,這些JAVA程序的生命週期都是這樣的:
    *編程人員在一定的操作系統上選擇一個合適的編輯軟件來實現源程序代碼並以.java擴展名保存在操作系統中,例如我們在中文win2k中用記事本編輯一個java源程序;
     *編程人員用JDK中的javac.exe來編譯這些源代碼,形成.class類(JSP文件是由容器調用JDK來編譯的);
     *直接運行這些類或將這些類佈署到WEB容器中去運行,並輸出結果。
    那麼,在這些過程中,JDK和JVM是如何將這些文件如何編碼和解碼並運行的呢?
    這裏,我們以中文win2k操作系統爲例說明JAVA類是如何來編碼和被解碼的。
     第一步,我們在中文win2k中用編輯軟件如記事本編寫一個Java源程序文件(包括以上五類JAVA程序),程序文件在保存時默認採用了操作系統默認支持GBK編碼格式(操作系統默認支持的格式爲file.encoding格式)形成了一個.java文件,也即,java程序在被編譯前,我們的JAVA 源程序文件是採用操作系統默認支持的file.encoding編碼格式保存的,java源程序中含有中文信息字符和英文程序代碼;要查看系統的 file.encoding參數,可以用以下代碼:
public class ShowSystemDefaultEncoding {
public static void main(String[] args) {
String encoding = System.getProperty("file.encoding");
System.out.println(encoding);
}}
     第二步,我們用JDK的javac.exe文件編譯我們的Java源程序,由於JDK是國際版的,在編譯的時候,如果我們沒有用-encoding參數指定我們的JAVA源程序的編碼格式,則javac.exe首先獲得我們操作系統默認採用的編碼格式,也即在編譯java程序時,若我們不指定源程序文件的編碼格式,JDK首先獲得操作系統的file.encoding參數(它保存的就是操作系統默認的編碼格式,如WIN2k,它的值爲GBK),然後JDK 就把我們的java源程序從file.encoding編碼格式轉化爲JAVA內部默認的UNICODE格式放入內存中。然後,javac把轉換後的 unicode格式的文件進行編譯成.class類文件,此時.class文件是UNICODE編碼的,它暫放在內存中,緊接着,JDK將此以 UNICODE編碼的編譯後的class文件保存到我們的操作系統中形成我們見到的.class文件。對我們來說,我們最終獲得的.class文件是內容以UNICODE編碼格式保存的類文件,它內部包含我們源程序中的中文字符串,只不過此時它己經由file.encoding格式轉化爲UNICODE格式了。
    這一步中,對於JSP源程序文件是不同的,對於JSP,這個過程是這樣的:即WEB容器調用JSP編譯器,JSP編譯器先查看 JSP文件中是否設置有文件編碼格式,如果JSP文件中沒有設置JSP文件的編碼格式,則JSP編譯器調用JDK先把JSP文件用JVM默認的字符編碼格式(也即WEB容器所在的操作系統的默認的file.encoding)轉化爲臨時的Servlet類,然後再把它編譯成UNICODE格式的class 類,並保存在臨時文件夾中。如:在中文win2k上,WEB容器就把JSP文件從GBK編碼格式轉化爲UNICODE格式,然後編譯成臨時保存的 Servlet類,以響應用戶的請求。
    第三步,運行第二步編譯出來的類,分爲三種情況:
    A、 直接在console上運行的類
    B、 EJB類和不可以直接運行的支持類(如JavaBean類)
    C、 JSP代碼和Servlet類
    D、 JAVA程序和數據庫之間
    下面我們分這四種情況來看。
    A、直接在console上運行的類
     這種情況,運行該類首先需要JVM支持,即操作系統中必須安裝有JRE。運行過程是這樣的:首先java啓動JVM,此時JVM讀出操作系統中保存的 class文件並把內容讀入內存中,此時內存中爲UNICODE格式的class類,然後JVM運行它,如果此時此類需要接收用戶輸入,則類會默認用 file.encoding編碼格式對用戶輸入的串進行編碼並轉化爲unicode保存入內存(用戶可以設置輸入流的編碼格式)。程序運行後,產生的字符串(UNICODE編碼的)再回交給JVM,最後JRE把此字符串再轉化爲file.encoding格式(用戶可以設置輸出流的編碼格式)傳遞給操作系統顯示接口並輸出到界面上。


    以上每一步的轉化都需要正確的編碼格式轉化,才能最終不出現亂碼現象。
    B、EJB類和不可以直接運行的支持類(如JavaBean類)
     由於EJB類和不可以直接運行的支持類,它們一般不與用戶直接交互輸入和輸出,它們常常與其它的類進行交互輸入和輸出,所以它們在第二步被編譯後,就形成了內容是UNICODE編碼的類保存在操作系統中了,以後只要它與其它的類之間的交互在參數傳遞過程中沒有丟失,則它就會正確的運行。


    C、JSP代碼和Servlet類
    經過第二步後,JSP文件也被轉化爲Servlets類文件,只不過它不像標準的Servlets一校存在於classes目錄中,它存在於WEB容器的臨時目錄中,故這一步中我們也把它做爲Servlets來看。
     對於Servlets,客戶端請求它時,WEB容器調用它的JVM來運行Servlet,首先,JVM把Servlet的class類從系統中讀出並裝入內存中,內存中是以UNICODE編碼的Servlet類的代碼,然後JVM在內存中運行該Servlet類,如果Servlet在運行的過程中,需要接受從客戶端傳來的字符如:表單輸入的值和URL中傳入的值,此時如果程序中沒有設定接受參數時採用的編碼格式,則WEB容器會默認採用ISO-8859- 1編碼格式來接受傳入的值並在JVM中轉化爲UNICODE格式的保存在WEB容器的內存中。Servlet運行後生成輸出,輸出的字符串是 UNICODE格式的,緊接着,容器將Servlet運行產生的UNICODE格式的串(如html語法,用戶輸出的串等)直接發送到客戶端瀏覽器上並輸出給用戶,如果此時指定了發送時輸出的編碼格式,則按指定的編碼格式輸出到瀏覽器上,如果沒有指定,則默認按ISO-8859-1編碼發送到客戶的瀏覽器上。


    D、Java程序和數據庫之間
    對於幾乎所有數據庫的JDBC驅動程序,默認的在JAVA程序和數據庫之間傳遞數據都是以ISO-8859-1爲默認編碼格式的,所以,我們的程序在向數據庫內存儲包含中文的數據時,JDBC首先是把程序內部的 UNICODE編碼格式的數據轉化爲ISO-8859-1的格式,然後傳遞到數據庫中,在數據庫保存數據時,它默認即以ISO-8859-1保存,所以,這是爲什麼我們常常在數據庫中讀出的中文數據是亂碼。


    3、分析常見的JAVA中文問題幾個必須清楚的原則
    首先,經過上面的詳細分析,我們可以清晰地看到,任何JAVA程序的生命期中,其編碼轉換的關鍵過程是在於:最初編譯成class文件的轉碼和最終向用戶輸出的轉碼過程。
    其次,我們必須瞭解JAVA在編譯時支持的、常用的編碼格式有以下幾種:
    *ISO-8859-1,8-bit, 同8859_1,ISO-8859-1,ISO_8859_1等編碼
    *Cp1252,美國英語編碼,同ANSI標準編碼
    *UTF-8,同unicode編碼
    *GB2312,同gb2312-80,gb2312-1980等編碼
    *GBK , 同MS936,它是gb2312的擴充
    及其它的編碼,如韓文、日文、繁體中文等。同時,我們要注意這些編碼間的兼容關體系如下:
    unicode 和UTF-8編碼是一一對應的關係。GB2312可以認爲是GBK的子集,即GBK編碼是在gb2312上擴展來的。同時,GBK編碼包含了20902個漢字,編碼範圍爲:0x8140-0xfefe,所有的字符可以一一對應到UNICODE2.0中來。
    再次,對於放在操作系統中的. java源程序文件,在編譯時,我們可以指定它內容的編碼格式,具體來說用-encoding來指定。注意:如果源程序中含有中文字符,而你用- encoding指定爲其它的編碼字符,顯然是要出錯的。用-encoding指定源文件的編碼方式爲GBK或gb2312,無論我們在什麼系統上編譯含有中文字符的JAVA源程序都不會有問題,它都會正確地將中文轉化爲UNICODE存儲在class文件中。
    
然後,我們必須清楚,幾乎所有的WEB容器在其內部默認的字符編碼格式都是以ISO-8859-1爲默認值的,同時,幾乎所有的瀏覽器在傳遞參數時都是默認以UTF-8的方式來傳遞參數的。所以,雖然我們的Java源文件在出入口的地方指定了正確的編碼方式,但其在容器內部運行時還是以ISO-8859-1來處理的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章