- はじめに
- 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.encodingsun.jnu.encodingsun.stdout.encodingsun.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.cjdk/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()) を元にして設定されます。