Java 讀取檔案時的編碼
Tags: java
Published: 2023-01-04 13:34:30

從 [JDK 18 開始](https://medium.com/@andbin/jdk-18-and-the-utf-8-as-default-charset-8451df737f90), Java 預設編碼不再是使用平台的編碼, 而是 [UTF-8 編碼]((https://medium.com/@andbin/jdk-18-and-the-utf-8-as-default-charset-8451df737f90)), 以下是分別以 JDK 17 取得預設編碼的結果：

```java
# jshell
|  Welcome to JShell -- Version 17.0.6
|  For an introduction type: /help intro

jshell> import java.nio.charset.Charset

jshell> Charset.defaultCharset()
$2 ==> x-windows-950
```

如果使用 JDK 18, 結果如下：

```java
# jshell
|  Welcome to JShell -- Version 18.0.1.1
|  For an introduction type: /help intro

jshell> import java.nio.charset.Charset

jshell> Charset.defaultCharset()
$2 ==> UTF-8
```

如果你的程式在讀取檔案時都沒有指定編碼, 就要注意是否會因為編碼關係而讀取錯誤。

## 以指定編碼讀取檔案

如果想要讀取不同編碼的檔案, 就必須在開啟檔案時設定編碼。以下以在 JDK 18 的環境下讀取 big5 編碼的檔案為例：

```
我是 big-5
```

要指定編碼, 可以使用 [java.nio.charset.Charset](https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/nio/charset/Charset.html) 類別的 [forName](https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/nio/charset/Charset.html#forName(java.lang.String)) 方法, 傳入編碼的名稱即可：

```java
import java.nio.charset.*;
import java.io.*;

public class RF {
  public static void main(String[] args) {
    char[] c = new char[1];
    try {
      FileReader f = new FileReader("test.txt", Charset.forName("big5"));
      while(f.read(c, 0, 1) != -1) {
        System.out.print(c[0]);
      }
    }
    catch(Exception e) {
    }
  }
}
```

可用的編碼名稱可以參考 [2 Supported Encodings](https://docs.oracle.com/en/java/javase/19/intl/supported-encodings.html#GUID-D29CF3AD-FC0B-465F-8897-C38C0395AB02) 的列表。

對於一些國際標準的編碼, 則是在 [java.nio.charset.StandarCharset](https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/nio/charset/StandardCharsets.html) 類別中已經有建好的編碼物件, 分別是 US_ASCII、ISO_8859_1、UTF_16、UTF_16BE、UTF_16LE、UTF_8, 可以直接取用。

## 查詢目前使用編碼

如果你擔心預設的編碼不是 UTF8, 也可以透過 Charset 的 [defaultCharset()](https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/nio/charset/Charset.html#defaultCharset()) 方法查看：

```java
jshell> import java.nio.charset.Charset

jshell> Charset.defaultCharset()
$2 ==> UTF-8
```

或是也可以檢查系統變數 [file.encoding](https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/lang/System.html#file.encoding)：

```java
jshell> System.getProperty("file.encoding")
$3 ==> "UTF-8"
```

系統變數 native.encoding 則是作業系統平台本身採用的編碼：

```java
jshell> System.getProperty("native.encoding")
$4 ==> "MS950"
```

MS950 就是 Windows 的 CP950, 也就是 Big5 編碼。

## 輸出時的編碼

輸出時預設會採用執行環境的編碼, JDK 18 開始可以由 [System.out.charset().name()](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/io/PrintStream.html#charset) 取得, 以下是簡單的測試程式：

```java
public class SysOutEncoding {
  public static void main(String args[]) {
    System.out.println(System.out.charset().name());
  }
}
```

以下是在 Powershell 7 的實際執行結果：

```
# chcp
Active code page: 65001
# java .\SysOutEncoding.java
UTF-8
```

如果改到 cmd 下執行：

```
>chcp
使用中的字碼頁: 950

>java SysOutEncoding.java
x-windows-950
```

不過要注意的是, 在 jshell 中, 似乎都是採用系統預設的編碼, 像是在已經設定為 UTF-8 的 Powershell 7 中：

```java
jshell> System.out.charset().name()
$1 ==> "x-windows-950"

jshell> System.getProperty("native.encoding")
$2 ==> "MS950"

jshell> System.out.println("測試")
����
```

由於輸出編碼與 shell 不同, 所以會變成亂碼。如果想要更改 `System.out` 的輸出編碼, 可以這樣做：

```java
jshell> System.setOut(new PrintStream(System.out, true, "UTF-8"))

jshell> System.out.println("測試")
測試
```

輸出結果就正確了。