讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議58:強烈建議使用UTF編碼 >

建議58:強烈建議使用UTF編碼

Java的亂碼問題由來已久,有點經驗的開發人員肯定都遇到過亂碼問題,有時是從Web上接收的亂碼,有時是從數據庫中讀取的亂碼,有時是在外部接口中接收到的亂碼文件,這些都讓我們困惑不已,甚至是痛苦不堪,看如下代碼:


public static void main(Stringargs)throws Exception{

String str=\"漢字\";

//讀取字節

byteb=str.getBytes(\"UTF-8\");

//重新生成一個新的字符串

System.out.println(new String(b));

}


Java文件是通過IDE工具默認創建的,編碼格式是GBK,大家想想看上面的輸出結果會是什麼?可能是亂碼吧?兩個編碼格式不相同。我們暫不公佈結果,先解釋一下Java中的編碼規則。Java程序涉及的編碼包括兩部分:

(1)Java文件編碼

如果我們使用記事本創建一個.java後綴的文件,則文件的編碼格式就是操作系統默認的格式。如果是使用IDE工具創建的,如Eclipse,則依賴於IDE的設置,Eclipse默認是操作系統編碼(Windows一般為GBK)。

(2)Class文件編碼

通過javac命令生成的後綴名為.class的文件是UTF-8編碼的UNICODE文件,這在任何操作系統上都是一樣的,只要是class文件就會是UNICODE格式。需要說明的是,UTF是UNICODE的存儲和傳輸格式,它是為了解決UNICODE的高位佔用冗余空間而產生的,使用UTF編碼就標誌著字符集使用的是UNICODE。

再回到我們的例子上,getBytes方法會根據指定的字符集提取出字節數組(這裡按照UNICODE格式來提取),然後程序又通過newString(bytebytes)重新生成一個字符串。來看看String這個構造函數:通過操作系統默認的字符集解碼指定的byte數組,構造一個新的String。結果已經很清楚了,如果操作系統是UTF-8編碼的話,輸出就是正確的,如果不是,則會是亂碼。由於這裡使用的是默認編碼GBK,那麼輸出的結果也就是亂碼了。我們再詳細分解一下運行步驟:

步驟1 創建Client.java文件。

該文件的默認編碼GBK(如果使用Eclipse,則可以在屬性查看到)。

步驟2 編寫代碼(如上)。

步驟3 保存,並使用javac編譯。

注意我們沒有使用\"javac-encoding GBK Client.java\"顯式聲明Java的編碼格式,javac會自動按照操作系統的編碼(GBK)讀取Client.java文件,然後將其編譯成.class文件。

步驟4 生成.class文件。

編譯結束,生成.class文件,並保存到硬盤上。此時.class文件使用的是UTF-8格式編碼的UNICODE字符集,可以通過javap命令閱讀class文件。其中「漢字」變量也已經由GBK編碼轉變成UNICODE格式了。

步驟5 運行main方法,提取「漢字」的字節數組。

「漢字」原本是按照UTF-8格式保存的,要再提取出來當然沒有任何問題了。

步驟6 重組字符串。

讀取操作系統的編碼格式(GBK),然後重新編碼變量b的所有字節。問題就在這裡產生了:因為UNICODE的存儲格式是兩個字節表示一個字符(注意這裡是指UCS-2標準),雖然GBK也是2個字節表示一個字符,但兩者之間沒有影射關係,要想做轉換只能讀取映射表,不能實現自動轉換——於是JVM就按照默認的編碼格式(GBK)讀取了UNICODE的兩個字節。

步驟7 輸出亂碼,程序運行結束。

問題清楚了,解決方案也隨之產生,方案有兩個。

步驟8 修改代碼。

明確指定編碼即可,代碼如下:


System.out.println(new String(b,\"UTF-8\"));


步驟9 修改操作系統的編碼方式。

各個操作系統的修改方式不同,不再贅述。

我們可以把從字符串讀取字節的過程看作是數據傳輸的需要(比如網絡、存儲),而重組字符串則是業務邏輯的需求,這樣就可使亂碼現場重現:通過JDBC讀取的字節數組是GBK的,而業務邏輯編碼時採用的是UTF-8,於是亂碼產生了。對於此類問題,最好的解決辦法就是使用統一的編碼格式,要麼都用GBK;要麼都用UTF-8,各個組件、接口、邏輯層都用UTF-8,拒絕獨樹一幟的情況。

問題解釋清楚了,我們再來看以下代碼:


public class Client{

public static void main(Stringargs)throws Exception{

String str=\"漢字\";

//讀取字節

byteb=str.getBytes(\"GB2312\");

//重新生成一個新的字符串

System.out.println(new String(b));

}

}


僅僅修改了讀取字節的編碼格式(修改成了GB2312格式的),結果會是怎樣的呢?又或者將其修改成GB18030,結果又是怎樣的呢?結果都是「漢字」,不是亂碼。哈哈,這是因為GB2312是中文字符集的V1.0版,GBK是V2.0版本,GB18030是V3.0版,版本是向下兼容的,只是它們包含的漢字數量不同而已,注意,UNICODE可不在這個序列之內的。

注意 一個系統使用統一的編碼。