java編碼詳解

舉個例子

我們在開發過程中,特別是多種編碼格式並存的情況下,很容易遇到亂碼問題。 假如有一個GBK編碼java文件,然後再使用-Dfile.encoding=GBK參數,寫入的文件中哪些是亂碼呢。那如果使用UFT-8編碼的java文件呢。

public class Main {
    static String content = "中文";

    public static void main(String[] args) throws IOException {
        OutputStreamWriter gbkWriter =new OutputStreamWriter(new FileOutputStream("GBK-FILE"),"GBK");

       // 情況1、2、5的結果肯定是一致的
        gbkWriter.write(content+"\n"); //(1)
        gbkWriter.write(new String(content.getBytes("GBK"),"GBK")+"\n");             // (2)
        gbkWriter.write(new String(content.getBytes("GBK"), "UTF-8")+"\n");          // (3)
        gbkWriter.write(new String(content.getBytes("UTF-8"),"GBK")+"\n");            // (4)
        gbkWriter.write(new String(content.getBytes("UTF-8"), "UTF-8")+"\n");   // (5)

        gbkWriter.flush();
        gbkWriter.close();

    }
}

  

java編譯到輸出

其實用一張圖就可以清晰的概括出從java文件編譯到輸出的過程  主要有3個地方的編碼轉換:

編譯過程

上圖①所示的位置,其實就是

javac -encoding xxx

的時候控制,如果你沒有顯示的指定編碼,那麼會根據當前操作系統的默認編碼格式進行編譯,一般windows是gbk,linux是UTF-8。如果這裏編碼指定錯了,那麼你的代碼很有可能出現中文亂碼問題,注意是很有可能,而不是絕對。原因後面會說到。編譯出來的class文件統一都是UTF-8格式

運行加載

當class文件加載的jvm的時候,也會進行字符串編碼轉換,和前面一樣,會使用操作系統默認的編碼格式,這裏的編碼不是指class文件的編碼,而是指java文件的編碼格式,類似於指定java文件是什麼編碼格式編譯爲class文件的。就是jvm參數:

java -Dfile.encoding=xxx

在java運行過程中,字符串在內存中則是使用Unicode(UTF-16)進行存儲的。而UTF-8轉換爲UTF-16是很簡單的過程。當我們標用String.getBytes()時候,則是把內存中的unicode轉換對應的字節數組。(如果沒有指定,則使用操作系統默認的編碼格式)。可以看出,把一個字符串從編譯到內存,其實是經歷的過程爲:

編譯文件編碼->加載jvm編碼->unicode

輸出

輸出的編碼則是在代碼中指定的。例如: OutputStreamWriter gbkWriter =new OutputStreamWriter(new FileOutputStream("GBK-FILE"),"GBK");

例子解析

如果理解上面的,我們再看看文章一開始的例子。舉幾個例子做說明,其他的情況也就逐類旁通。

例子1

  • java文件編碼:GBK,

  • javac -encoding GBK

  • java -Dfile.encoding=GBK 那麼使用GBK編碼查看輸出文件

  • (1)正常

  • (2)正常

  • (3)亂碼

  • (4)亂碼

  • (5)正常

情況(1)

情況1是比較好理解的,因爲java文件編碼、編譯、加載都是使用GBK,加載到內存中Unicode肯定也是正常的,那麼打印出來也是正常的。

情況(2)和情況(5)

在情況1的前提下(即加載到內存中是正常的),在jvm中使用GBK解碼在編碼肯定是正常的。

情況(3)和情況(4)

在情況1的前提下,使用不同的解碼和編碼,肯定是亂碼

特殊情況

當我們使用UTF-8的格式打開文件的時候,情況(4)是正常的,其餘都是亂碼。其實是因爲先使用unicode進行轉換爲UTF-8格式的Byte數組,生成的字符串雖然亂碼和寫文件的格式都是GBK,相當於原封不動的UTF-8格式的byte數組寫到文件中,所以就會出現這個情況

小節

這個例子就是直至加載到內存都是正常的情況下,在jvm內進行編碼和解碼導致亂碼的情況

例子2

  • java文件編碼:UTF-8,

  • javac -encoding GBK

  • java -Dfile.encoding=UTF-8

那麼使用GBK編碼查看輸出文件

  • (1)亂碼

  • (2)亂碼

  • (3)正常

  • (4)亂碼

  • (5)亂碼

情況(1)

情況1是亂碼,說明字符串加載到內存中就已經是亂碼了。因爲UFT-8格式使用GBK進行編碼,在生成class文件就已經是亂碼了。

情況(3)

情況3爲什麼又是正常的呢,其實這是誤打誤撞類型。個人理解的造成這個情況的原因有:

  1. java文件的編碼正好和jvm加載文件編碼格式是一樣的

  2. javac過程,相當於一個UTF-8->GBK格式轉換,而content.getBytes("GBK"), "UTF-8")又相當於GBK->UTF-8的轉換,兩次轉換正好相互抵消。

使用UTF-8編碼查看輸出文件

  • (1)正常

  • (2)正常

  • (3)亂碼

  • (4)亂碼

  • (5)正常

爲什麼是使用UTF-8打開情況1不是亂碼了呢,其實和上面誤打誤撞,只不過之前發生在內存中的GBK->UTF-8換爲我們在打開文件的時候進行的編碼轉換,情況1、2、5結果肯定一致的


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章