- はじめに
- JDK16におけるエンコーディング関連システムプロパティ
- JDK17におけるエンコーディング関連システムプロパティ
- JDK18 UTF-8 by Default
- JDK19 におけるコンソールI/Oの文字コード
- まとめ
はじめに
JDK 18 の、JEP 400: UTF-8 by Default 前後で、エンコーディング関連のシステムプロパティにつらつらと変更があり、混乱するので、ここにまとめておきます。
JDK 19 以降のエンコーディング関連システムプロパティは、なんと7個も存在します。
- sun.jnu.encoding
- native.encoding
- file.encoding
- stdout.encoding
- stdout.encoding
- sun.stderr.encoding
- sun.stdout.encoding
現状のシステムプロパティは多くて良くわからなくなるので、JDK 16 時代の内部実装から順を追って見ていきましょう。
JDK16におけるエンコーディング関連システムプロパティ
JEP 400: UTF-8 by Default 以前はシンプルな時代でした。
エンコーディング関連システムプロパティは以下があります。
file.encoding
sun.jnu.encoding
sun.stdout.encoding
sun.stderr.encoding
この中の sun.*
は非公式オプションで、大抵は file.encoding
だけを気にすれば良かったです。
実装はどうなっているかと言えば、これらのシステムプロパティは jdk/src/java.base/share/classes/jdk/internal/util/SystemProps.java
で以下のように設定されます。
// Platform defined encoding cannot be overridden on the command line put(props, "sun.jnu.encoding", raw.propDefault(Raw._sun_jnu_encoding_NDX)); // Add properties that have not been overridden on the cmdline putIfAbsent(props, "file.encoding", ((raw.propDefault(Raw._file_encoding_NDX) == null) ? raw.propDefault(Raw._sun_jnu_encoding_NDX) : raw.propDefault(Raw._file_encoding_NDX))); // Use platform values if not overridden by a commandline -Dkey=value // In no particular order putIfAbsent(props, "sun.stdout.encoding", raw.propDefault(Raw._sun_stdout_encoding_NDX)); putIfAbsent(props, "sun.stderr.encoding", raw.propDefault(Raw._sun_stderr_encoding_NDX));
raw
はネイティブ側で設定された配列を表し、jdk/src/java.base/share/native/libjava/System.c
で以下のように設定されたものです。
PUTPROP(propArray, _sun_jnu_encoding_NDX, sprops->sun_jnu_encoding); PUTPROP(propArray, _file_encoding_NDX, sprops->encoding); PUTPROP(propArray, _sun_stdout_encoding_NDX, sprops->sun_stdout_encoding); PUTPROP(propArray, _sun_stderr_encoding_NDX, sprops->sun_stderr_encoding);
sprops
がシステムプロパティを表し、これは jdk/src/java.base/share/native/libjava/java_props.h
にて以下のように定義された構造体です。
typedef struct { // ... char *encoding; char *sun_jnu_encoding; char *stdout_encoding; char *stderr_encoding; // ... } java_props_t;
この構造体 sprops
は、ホスト環境に応じて以下のネイティブコードで構築されます。
jdk/src/java.base/unix/native/libjava/java_props_md.c
jdk/src/java.base/windows/native/libjava/java_props_md.c
主に、Linux の場合は nl_langinfo()
、Windows の場合は GetLocaleInfo()
などのAPIにより、ホスト環境のロケール情報からエンコーディングのプロパティが設定されます。
java_props_t | Linux | Windows |
---|---|---|
encoding | nl_langinfo() から取得 |
GetLocaleInfo() から取得 |
sun_jnu_encoding | encoding の値 | GetLocaleInfo() から取得 |
stdout_encoding | 未割当 | コンソール割り当てがあればGetConsoleCP() から取得 |
stderr_encoding | 未割当 | コンソール割り当てがあればGetConsoleCP() から取得 |
Windows の場合の sun.stdout.encoding
sun.stderr.encoding
について補足しておきます。
jdk/src/java.base/windows/native/libjava/java_props_md.c
の以下のコードで sun.stdout.encoding
と sun.stderr.encoding
が設定されます。
hStdOutErr = GetStdHandle(STD_OUTPUT_HANDLE); if (hStdOutErr != INVALID_HANDLE_VALUE && GetFileType(hStdOutErr) == FILE_TYPE_CHAR) { sprops.sun_stdout_encoding = getConsoleEncoding(); } hStdOutErr = GetStdHandle(STD_ERROR_HANDLE); if (hStdOutErr != INVALID_HANDLE_VALUE && GetFileType(hStdOutErr) == FILE_TYPE_CHAR) { if (sprops.sun_stdout_encoding != NULL) sprops.sun_stderr_encoding = sprops.sun_stdout_encoding; else sprops.sun_stderr_encoding = getConsoleEncoding(); }
GetFileType(hStdOutErr) == FILE_TYPE_CHAR
では、ファイルが文字ファイルであるかを判定しており、文字ファイルの場合、対象のハンドルは通常、コンソールまたは LPT デバイスとなります(コンソールの割り当て有無を判定)。
つまり、コンソール割り当てがある場合、getConsoleEncoding()
の中で WIN API GetConsoleCP()
から取得したコードポイントを設定します。
Linux の場合は sun.stdout.encoding
sun.stderr.encoding
の割り当ては行われません。
なお、sun_jnu_encoding
は、MacOSの場合 UTF-8
で固定されています。
#ifdef MACOSX sprops.sun_jnu_encoding = "UTF-8"; #else sprops.sun_jnu_encoding = sprops.encoding; #endif
JDK17におけるエンコーディング関連システムプロパティ
JDK17 では UTF-8 by Default の前準備として JDK-8265989 System property for the native character encoding name でシステムプロパティ native.encoding
が追加されました。
jdk/src/java.base/share/classes/jdk/internal/util/SystemProps.java
には、native.encoding
の設定が追加され、その設定値は file.encoding
と同じ値になっています(file.encoding
に COMPAT
指定された場合のエンコーディングとなる)。
// Platform defined encoding cannot be overridden on the command line put(props, "sun.jnu.encoding", raw.propDefault(Raw._sun_jnu_encoding_NDX)); var nativeEncoding = ((raw.propDefault(Raw._file_encoding_NDX) == null) ? raw.propDefault(Raw._sun_jnu_encoding_NDX) : raw.propDefault(Raw._file_encoding_NDX)); put(props, "native.encoding", nativeEncoding); // Add properties that have not been overridden on the cmdline putIfAbsent(props, "file.encoding", nativeEncoding); putIfAbsent(props, "sun.stdout.encoding", raw.propDefault(Raw._sun_stdout_encoding_NDX)); putIfAbsent(props, "sun.stderr.encoding", raw.propDefault(Raw._sun_stderr_encoding_NDX));
native.encoding
はJava側だけのシステムプロパティであり、ネイティブ側のコードに変更はありません。
JDK17では、さらに、JDK-8264208 Console charset API で Console
の Charset
の変更が追加されました。
この変更似合わせて、jdk/src/java.base/unix/native/libjava/java_props_md.c
に以下が追加されました。
if (isatty(STDOUT_FILENO) == 1) { sprops.sun_stdout_encoding = sprops.encoding; } if (isatty(STDERR_FILENO) == 1) { sprops.sun_stderr_encoding = sprops.encoding; }
Linux において、今までは未設定だった sun.stdout.encoding
sun.stderr.encoding
が、コンソールの接続がある場合(isatty() == 1
)、 に sprops.encoding
が設定されるようになりました(プラットフォームのロケールからの値)。
Windows側の jdk/src/java.base/windows/native/libjava/java_props_md.c
にも以下のような変更が入っており、エンコーディングのプロパティの文字列値が変更されています。
switch (codepage) { case 0: case 65001: // <- 追加 strcpy(ret, "UTF-8"); break;
JDK18 UTF-8 by Default
JDK18 では、デフォルトの文字コードが UTF-8 となりました(JEP 400: UTF-8 by Default)。
これにより Charset.defaultCharset()
が(Windowsプラットフォームであろうと)UTF-8
を返すようになりました。
ただし、コンソールI/O に関しては別で、System.out
と System.err
の文字セットは、旧来通り環境依存になります。
UTF-8 by Default では、file.encoding
に COMPAT
を設定した場合は、従来互換の文字セットが採用されます。これは jdk/src/java.base/share/classes/jdk/internal/util/SystemProps.java
で以下のように実装されています。
// Platform defined encoding cannot be overridden on the command line put(props, "sun.jnu.encoding", raw.propDefault(Raw._sun_jnu_encoding_NDX)); var nativeEncoding = ((raw.propDefault(Raw._file_encoding_NDX) == null) ? raw.propDefault(Raw._sun_jnu_encoding_NDX) : raw.propDefault(Raw._file_encoding_NDX)); put(props, "native.encoding", nativeEncoding); // "file.encoding" defaults to "UTF-8", unless specified in the command line // where "COMPAT" designates the native encoding. var fileEncoding = props.getOrDefault("file.encoding", "UTF-8"); if ("COMPAT".equals(fileEncoding)) { put(props, "file.encoding", nativeEncoding); } else { putIfAbsent(props, "file.encoding", fileEncoding); } putIfAbsent(props, "sun.stdout.encoding", raw.propDefault(Raw._sun_stdout_encoding_NDX)); putIfAbsent(props, "sun.stderr.encoding", raw.propDefault(Raw._sun_stderr_encoding_NDX));
jdk/src/java.base/share/native/libjava/System.c
は以下のようになりました。
#ifdef MACOSX /* * Since sun_jnu_encoding is now hard-coded to UTF-8 on Mac, we don't * want to use it to overwrite file.encoding */ PUTPROP(propArray, _file_encoding_NDX, sprops->encoding); #else PUTPROP(propArray, _file_encoding_NDX, sprops->sun_jnu_encoding); #endif PUTPROP(propArray, _sun_jnu_encoding_NDX, sprops->sun_jnu_encoding); /* * file encoding for stdout and stderr */ PUTPROP(propArray, _sun_stdout_encoding_NDX, sprops->sun_stdout_encoding); PUTPROP(propArray, _sun_stderr_encoding_NDX, sprops->sun_stderr_encoding);
そもそも jdk/src/java.base/unix/native/libjava/java_props_md.c
で以下のようになっているので、上の条件付きコンパイルは意味を成していないと思われますが。
#ifdef MACOSX sprops.sun_jnu_encoding = "UTF-8"; #else sprops.sun_jnu_encoding = sprops.encoding; #endif if (isatty(STDOUT_FILENO) == 1) { sprops.sun_stdout_encoding = sprops.encoding; } if (isatty(STDERR_FILENO) == 1) { sprops.sun_stderr_encoding = sprops.encoding; }
JDK19 におけるコンソールI/Oの文字コード
JDK 19 では、JDK-8283620 System.out does not use the encoding/charset specified in the Javadoc により、旧来の非公式オプションであった sun.stdout.encoding
と sun.stderr.encoding
が以下のように名前を変えて公式オプションになりました。
stdout.encoding
: 標準出力ストリーム(System.out)で使用されるエンコーディングstderr.encoding
: 標準エラーストリーム(System.err)で使用されるエンコーディング
旧来の sun.stdout.encoding
と sun.stderr.encoding
は stdout.encoding
と stderr.encoding
にフォールバックされるため、sum.*
を使い続けることもできます。
jdk/src/java.base/share/classes/jdk/internal/util/SystemProps.java
は以下のようになりました。
JDK16 時代と比べるとぐちゃぐちゃです。
// Platform defined encoding cannot be overridden on the command line put(props, "sun.jnu.encoding", raw.propDefault(Raw._sun_jnu_encoding_NDX)); var nativeEncoding = ((raw.propDefault(Raw._file_encoding_NDX) == null) ? raw.propDefault(Raw._sun_jnu_encoding_NDX) : raw.propDefault(Raw._file_encoding_NDX)); put(props, "native.encoding", nativeEncoding); // "file.encoding" defaults to "UTF-8", unless specified in the command line // where "COMPAT" designates the native encoding. var fileEncoding = props.getOrDefault("file.encoding", "UTF-8"); if ("COMPAT".equals(fileEncoding)) { put(props, "file.encoding", nativeEncoding); } else { putIfAbsent(props, "file.encoding", fileEncoding); } // "stdout/err.encoding", prepared for System.out/err. For compatibility // purposes, substitute them with "sun.*" if they don't exist. If "sun.*" aren't // available either, fall back to "native.encoding". putIfAbsent(props, "stdout.encoding", props.getOrDefault("sun.stdout.encoding", raw.propDefault(Raw._stdout_encoding_NDX))); putIfAbsent(props, "stdout.encoding", nativeEncoding); putIfAbsent(props, "stderr.encoding", props.getOrDefault("sun.stderr.encoding", raw.propDefault(Raw._stderr_encoding_NDX))); putIfAbsent(props, "stderr.encoding", nativeEncoding);
ネイティブ側の実装からは sun.stdout.encoding
と sun.stderr.encoding
は消え去り、以下の構造体定義となっています。
typedef struct { char *encoding; char *sun_jnu_encoding; char *stdout_encoding; // 旧 sun_stdout_encoding char *stderr_encoding; // 旧 sun_stderr_encoding } java_props_t;
これに合わせて、sun_stdout_encoding
sun_stderr_encoding
は stdout.encoding
と stderr.encoding
にリネームされています。
stdout.encoding
と stderr.encoding
は、System.out
と System.err
の初期化で以下のように設定されます(src/java.base/share/classes/java/lang/System.java
)。
setOut0(newPrintStream(fdOut, props.getProperty("stdout.encoding"))); setErr0(newPrintStream(fdErr, props.getProperty("stderr.encoding")));
setOut0()
と setErr0()
はネイティブコードを経由して System.out
と System.err
に PrintStream
インスタンスを割り当てます(static final
な変数のためネイティブに設定)。
Console
クラスも GetPropertyAction.privilegedGetProperty("stdout.encoding")
として取得した文字セットが設定されます。
System.out
と System.err
、Console
の何れも、stdout.encoding
と stderr.encoding
に値が無い場合(コンソールの接続が無い場合)は、JDK17 で追加された native.encoding
の値(デフォルトは file.encoding
と同じ値)が設定されます。
現在のJDK24のソースでは、native.encoding
の値ではなく、UTF-8
を直接設定するように変更されています。
まとめ
JDK17以降、エンコーディング関連のシステムプロパティは色々と変更されてきました。
システムプロパティ | 説明 |
---|---|
sun.jnu.encoding |
nl_langinfo() /GetLocaleInfo() から取得したロケール情報から設定(非公開)。主にファイルパスやProcess コマンドのエンコーディングに使用される |
native.encoding |
JDK17で追加。基本的に sun.jnu.encoding と同じ。COMPAT 指定された場合のエンコーディング。 |
file.encoding |
UTF-8 固定。引数でCOMPAT と指定された場合は native.encoding の値 |
stdout.encoding |
コンソール接続がある場合、nl_langinfo() /GetConsoleCP() の値から設定 |
stderr.encoding |
コンソール接続がある場合、nl_langinfo() /GetConsoleCP() の値から設定 |
sun.stdout.encoding |
stdout.encoding に置き換え(非公開) |
sun.stderr.encoding |
stderr.encoding に置き換え(非公開) |
ファイル入出力では Charset defaultCharset()
が使われ、この値は基本的に file.encoding
の値(デフォルトUTF-8
)が使われます。
標準出力 System.out
と System.err
及び Console
には、stdout.encoding
stderr.encoding
の値が使われます。この値は、Linuxの場合はロケール情報(nl_langinfo()
)、Windows では、接続されているコンソールのコードポイント値(GetConsoleCP()
) を元にして設定されます。