天天看點

web應用中文亂碼問題的原因分析

為了讓使用Java語言編寫的程式能在各種語言的平台下運作,Java在其内部使用Unicode字元集來表示字元,這樣就存在Unicode字元集和本地字元集進行轉換的過程。當在Java中讀取字元資料的時候,需要将本地字元集編碼的資料轉換為Unicode編碼,而在輸出字元資料的時候,則需要将Unicode編碼轉換為本地字元集編碼。

例如,在中文系統下,從控制台讀取一個字元“中”,實際上讀取的是“中”的GBK編碼0xD6D0,在Java語言中要将GBK編碼轉換為Unicode編碼0x4E2D,此時,在記憶體中,字元“中”對應的數值就是0x4E2D,當我們向控制台輸出字元時,Java語言将Unicode編碼再轉換為GBK編碼,輸出到控制台,中文系統再根據GBK字元集畫出相應的字元。

從上述過程來看,讀取和寫入的過程是可逆的,那麼理應不會出現中文亂碼問題。然而,實際應用的情形,比上述過程要複雜得多。在Web應用中,通常都包括了浏覽器、Web伺服器、Web應用程式和資料庫等部分,每一部分都有可能使用不同的字元集,進而導緻字元資料在各種不同的字元集之間轉換時,出現亂碼的問題。

在Java語言中,不同字元集編碼的轉換,都是通過Unicode編碼作為中介來完成的。例如,GBK編碼的字元“中”要轉換為ISO-8859-1(同ISO8859-1)編碼,其過程如下:

(1)因為在Java中的字元,都是用Unicode來表示的,是以GBK編碼的字元“中”要轉換為Unicode表示:0xD6D0->0x4E2D。

(2)将字元“中”的Unicode編碼轉換為ISO-8859-1編碼,因為Unicode編碼0x4E2D在ISO-8859-1中沒有對應的編碼,于是得到0x3f,也就是字元“?”。

下面的代碼示範了這一過程:

//GBK編碼的字元“中”轉換為Unicode編碼表示

String str="中";

//将字元“中”的Unicode編碼轉換為ISO-8859-1編碼

byte[] b=str.getBytes("ISO-8859-1");

for(int i=0;i<b.length;i++)

{

    //輸出轉換後的二進制代碼。

    System.out.print(b[i]);

}

當從Unicode編碼向某個字元集轉換時,如果在該字元集中沒有對應的編碼,則得到0x3f(即問号字元?)。這就是為什麼有時候我們輸入的是中文,在輸出時卻變成了問号。

從其他字元集向Unicode編碼轉換時,如果這個二進制數在該字元集中沒有辨別任何的字元,則得到的結果是0xfffd。例如一個GBK的編碼值0x8140,從GB2312向Unicode轉換,然而由于0x8140不在GB2312字元集的編碼範圍(0xa1a1-0xfefe),當然也就沒有對應任何的字元,是以轉換後會得到0xfffd。下面的代碼示範了這一過程。

//構造一個二進制資料。

byte[] buf={(byte)0x81,(byte)0x40,(byte)0xb0,(byte)0xa1};

//将二進制資料按照GB2312向Unicode編碼轉換。

String str=new String(buf,"GB2312");

for(int i=0;i<str.length();i++)

    //取出字元串中的每個Unicode編碼的字元。

    char ch=str.charAt(i);

    //将該字元對應的Unicode編碼以十六進制的形式輸出。

    System.out.print(Integer.toHexString((int)ch));

    System.out.print("--");

    //輸出該字元。

    System.out.println(ch);

在輸出字元和字元串的時候,會從Unicode編碼向中文系統預設的編碼GBK轉換,由于Unicode編碼0xfffd在GBK字元集中沒有對應的編碼,于是得到0x3f,輸出字元“?”。最後輸出的結果如下:

fffd--?

40--@

554a--啊

從上述所知,由于存在着多種不同的字元集,在各種字元集之間進行轉換,就有可能出現亂碼,同樣是中文字元集GB2312和GBK,由于編碼範圍的不同,某些字元在轉換時也會出現亂碼。

在一個使用了資料庫的Web應用程式中,亂碼可能會在多個環節産生。由于浏覽器會根據本地系統預設的字元集來送出資料,而Web容器預設采用的是ISO-8859-1的編碼方式解析POST資料,在浏覽器送出中文資料後,Web容器會按照ISO-8859-1字元集來解碼資料,在這一環節可能會導緻亂碼的産生。由于大多數資料庫的JDBC驅動程式預設采用ISO-8859-1的編碼方式在Java程式和資料庫之間傳遞資料,我們的程式在向資料庫中存儲包含中文的資料時,JDBC驅動首先将程式内部的Unicode編碼格式的資料轉化為ISO-8859-1的格式,然後傳遞到資料庫中,在這一環節可能會導緻亂碼的産生。目前流行的關系型資料庫系統都支援資料庫編碼,也就是說在建立資料庫時可以指定它自己的字元集設定,資料庫的資料以指定的編碼形式存儲。當JDBC驅動向資料庫中儲存資料時,有可能還會發生字元集的轉換。正是由于在Web應用程式運作過程中,輸入的中文字元需要在不同的字元集之間來回轉換,也就導緻了中文亂碼問題的頻繁出現。

圖17-1描述了在Web應用的請求響應過程中,發生的字元編碼轉換過程,其中浏覽器是IE 6.0,Web容器的是Tomcat 6.0.16。

從圖17-1描述的過程中可以看到,如果在Web應用程式中不指定任何的字元集,從浏覽器端傳來的中文字元,輸出回浏覽器時,可以正常顯示(以簡體中文的方式檢視網頁)。然而,事情并沒有這麼簡單,在Servlet/JSP中,可能存在着直接寫入的或從其他來源讀取的中文字元,如果這些字元對應的Unicode碼是從GB2312編碼轉換而來,那麼以ISO-8859-1編碼方式輸出,這些字元将不能正常顯示。是以對于中文的處理,應該在圖17-1②和⑤的位置明确指定使用GB2312或GBK字元集。

圖17-1  在Web請求響應過程中,中文字元編碼的轉換過程若轉載請注明出處!若有疑問,請回複交流!