java編碼詳解

舉個例子

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

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

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

       // 狀況一、二、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個地方的編碼轉換:linux

編譯過程

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

javac -encoding xxx數組

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

運行加載

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

java -Dfile.encoding=xxx操作系統

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

編譯文件編碼->加載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換爲咱們在打開文件的時候進行的編碼轉換,狀況一、二、5結果確定一致的

小結

和例子1不同,例子2是在生成class文件就已是亂碼的狀況。這就是前面所說的,生成class亂碼,但輸出不必定是亂碼。

總結

一句話:必定要保證java文件、編譯、加載class文件、輸出的編碼格式一致。若是出現了亂碼,能夠從這幾個階段進行排查。

相關文章
相關標籤/搜索