JVM 預設編碼


如果在正體中文 Windows 中,使用以下的程式來讀取內含「測試」文字的檔案:

package cc.openhome;

import static java.lang.System.out; 
import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        try(BufferedReader reader = new BufferedReader(new FileReader("sample.txt"))) {
            out.println(reader.readLine());
        }
    }
}

如果文字檔案是使用 MS950 儲存,那上面的程式在命令提示字元中,可以正確讀出並顯示「測試」,如果文字檔案不是 MS950,就會顯示亂碼,例如,假設是 UTF-8 儲存「測試」的文字檔案:

C:\workspace>java cc.openhome.Main
皜祈岫

有些 API 若不指定編碼,通常會使用 JVM 預設編碼,預設會與作業系統預設編碼相同,像是 String 建構式、 getBytes() 方法或這邊看到的 FileReader 等(其他還有 java.iojava.utiljava.net 等套件中的一些 API),可以使用 Charset.defaultCharset() 取得預設編碼。

在啟動 JVM 時,其實可以使用 -Dfile.encoding 指定 JVM 預設編碼,例如:

C:\workspace>java -Dfile.encoding=UTF-8 cc.openhome.Main

也可以使用 InputStreamReader,將讀入的位元組指定編碼進行字串轉換。例如:

package cc.openhome;

import static java.lang.System.out;

import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        try(BufferedReader reader = new BufferedReader(
                new InputStreamReader(
                        new FileInputStream("sample.txt"), "UTF-8"));) {
            out.println(reader.readLine());
        }
    }
}

System.out 在輸出字串時,若是出現亂碼,可能的原因之一是,System.out 採用的編碼與連結的標準輸出不對應,基本上,System.out 採用 JVM 預設編碼,若 JVM 預設編碼為 Big5,System.out 就採用 Big5,此時若連結的標準輸出採用 UTF-8 的話,就會產生亂碼,這時可以透過 System.setOut() 結合 PrintStream 來設定編碼:

System.setOut(new PrintStream(System.out, true, "UTF-8"));

這類情況常發生在 IDE 之中,例如,在 Eclipse 中,若執行時設定的主控台編碼設定為 Big5(可在 Run As/Run Configurations/Common 中設定),然而 System.out 採用 JVM 預設編碼為 UTF-8,輸出文字時就會是亂碼,反之亦然。

如果命令提示字元採用的是 Big5,遇到 Big5 以外的字元,顯示上就會出現亂碼,這時必須調整命令提示字元編碼,並且配合正確的字型設定,才能正確顯示字元,例如,在〈你的原始碼是什麼編碼?〉中談到的,新版的 Windows 10 可以藉由地區設定,將 Console 預設為 UTF-8:

你的原始碼是什麼編碼?

在較新版的 JDK 中,有些 API 直接採用 UTF-8 為預設編碥,而不是根據 JVM 預設編碼,例如 Files.readAllLines()Files.newBufferedReader() 等。

在撰寫這篇文件的時間點上,有個〈JEP draft: Use UTF-8 as default Charset〉建立於 2017/08/31,提議未來 JDK 直接將 UTF-8 作為 JVM 預設編碼。