天天看點

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來處理的。

4、中文問題的分類及其建議最優解決辦法

了解以上JAVA處理檔案的原理之後,我們就可以提出了一套建議最優的解決漢字問題的辦法。

我們的目标是:我們在中文系統中編輯的含有中文字元串或進行中文處理的JAVA源程式經編譯後可以移值到任何其它的作業系統中正确運作,或拿到其它作業系統中編譯後能正确運作,能正确地傳遞中文和英文參數,能正确地和資料庫交流中英文字元串。

我們的具體思路是:在JAVA程式轉碼的入口和出口及JAVA程式同使用者有輸入輸出轉換的地方限制編碼方法使之正确即可。

具體解決辦法如下:

1、 針對直接在console上運作的類

對于這種情況,我們建議在程式編寫時,如果需要從使用者端接收使用者的可能含有中文的輸入或含有中文的輸出,程式中應該采用字元流來處理輸入和輸出,具體來說,應用以下面向字元型節點流類型:

對檔案:FileReader,FileWrieter

其位元組型節點流類型為:FileInputStream,FileOutputStream

對記憶體(數組):CharArrayReader,CharArrayWriter

其位元組型節點流類型為:ByteArrayInputStream,ByteArrayOutputStream

對記憶體(字元串):StringReader,StringWriter

對管道:PipedReader,PipedWriter

其位元組型節點流類型為:PipedInputStream,PipedOutputStream

同時,應該用以下面向字元型處理流來處理輸入和輸出:

BufferedWriter,BufferedReader

其位元組型的處理流為:BufferedInputeStream,BufferedOutputStream

InputStreamReader,OutputStreamWriter

其位元組型的處理流為:DataInputStream,DataOutputStream

其中InputStreamReader和InputStreamWriter用于将位元組流按照指定的字元編碼集轉換到字元流,如:

InputStreamReader in = new InputStreamReader(System.in,"GB2312");

OutputStreamWriter out = new OutputStreamWriter (System.out,"GB2312");

例如:采用如下的示例JAVA編碼就達到了要求:

//Read.java

import java.io.*;

public class Read {

public static void main(String[] args) throws IOException {

String str = "/n中文測試,這是内部寫死的串"+"/ntest english character";

String strin= "";

BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in,"gb2312")); //設定輸入接口按中文編碼

BufferedWriter stdout = new BufferedWriter(new OutputStreamWriter(System.out,"gb2312")); //設定輸出接口按中文編碼

stdout.write("請輸入:");

stdout.flush();

strin = stdin.readLine();

stdout.write("這是從使用者輸入的串:"+strin);

stdout.write(str);

}}

同時,在編譯程式時,我們用以下方式來進行:

javac -encoding gb2312 Read.java

2、 針對EJB類和不可以直接運作的支援類(如JavaBean類)

由于這種類它們本身被其它的類調用,不直接與使用者互動,故對這種類來說,我們的建議的處理方式是内部程式中應該采用字元流來處理程式内部的中文字元串(具體如上面一節中一樣),同時,在編譯類時用-encoding gb2312參數訓示源檔案是中文格式編碼的即可。

3、 針對Servlet類

針對Servlet,我們建議用以下方法:

在編譯Servlet類的源程式時,用-encoding指定編碼為GBK或 GB2312,且在向使用者輸出時的編碼部分用response對象的setContentType("text/html;charset=GBK"); 或gb2312來設定輸出編碼格式,同樣在接收使用者輸入時,我們用request.setCharacterEncoding("GB2312");這樣 無論我們的servlet類移植到什麼作業系統中,隻有用戶端的浏覽器支援中文顯示,就可以正确顯示。如下是一個正确的示例:

//HelloWorld.java

package hello;

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"); //設定輸入編碼格式

response.setContentType("text/html;charset=GB2312"); //設定輸出編碼格式

PrintWriter out = response.getWriter(); //建議使用PrintWriter輸出

out.println("Hello World! This is created by Servlet!測試中文!");

}

public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException

String name = request.getParameter("name");

String id = request.getParameter("id");

if(name==null) name="";

if(id==null) id="";

out.println("你傳入的中文字串是:" + name);

out.println("你輸入的id是:" + id);

public void destroy() { }

請用javac -encoding gb2312 HelloWorld.java來編譯此程式。

4、 JAVA程式和資料庫之間

為避免JAVA程式和資料庫之間資料傳遞出現亂碼現象,我們建議采用以下最優方法來處理:

1、 對于JAVA程式的處理方法按我們指定的方法處理。

2、 把資料庫預設支援的編碼格式改為GBK或GB2312的。

如:在mysql中,我們可以在配置檔案my.ini中加入以下語句實作:

在[mysqld]區增加:

default-character-set=gbk

并增加:

[client]

在SQL Server2K中,我們可以将資料庫預設的語言設定為Simplified Chinese來達到目的。

5、 針對JSP代碼

由于JSP是在運作時,由WEB容器進行動态編譯的,如果我們沒有指定JSP源檔案的編碼格式,則JSP編譯器會獲得服務 器作業系統的file.encoding值來對JSP檔案編譯的,它在移植時最容易出問題,如在中文win2k中可以很好運作的jsp檔案拿到英文 linux中就不行,盡管用戶端都是一樣的,那是因為容器在編譯JSP檔案時擷取的作業系統的編碼不同造成的(在中文wink中的 file.encoding和在英文Linux中file.encoding是不同的,且英文Linux的file.encoding對中文不支援,是以 編譯出來的JSP類就會有問題)。網絡上讨論的大多數是此類問題,多是因為JSP檔案移植平台時不能正确顯示的問題,對于這類問題,我們了解了JAVA中 程式編碼轉換的原理,解決起來就容易多了。我們建議的解決辦法如下:

1、我們要保證JSP向用戶端輸出時是采用中文編碼方式輸出的,即無論如何我們首先在我們的JSP源代編中加入以下一行:

<%@page contentType=”text/html;charset=gb2312″%>

2、為了讓JSP能正确獲得傳入的參數,我們在JSP源檔案頭加入下面一句:

<%request.setCharacterEncoding(”GB2312″);%>

3、為了讓JSP編譯器能正确地解碼我們的含有中文字元的JSP檔案,我們需要在JSP源檔案中指定我們的JSP源檔案的編碼格式,具體來說,我們在JSP源檔案頭上加入下面的一句即可:

這是JSP規範2.0新增加的指令。

我們建