[Java][文字コード] Java8 の非互換な文字コード変換
文字コード関連でちょっと調べていたら、未定義文字を変換したときの挙動が Java8 と Java7 までで異なることを発見したのでメモ。
Java7 まではネイティブコードの1文字が概ね U+FFFD 1つに変換される。
# 半角の未定義文字 1B284920 -> FF60 (ISO-2022-JP) 8EA0 -> FFFD (EUC-JP) A0 -> FFFD (Shift_JIS) A0 -> FFFD (windows-31j) A0 -> FFFD (x-SJIS_0213) # 半角の定義文字(参考) 1B284921 -> FF61 (ISO-2022-JP) 8EA1 -> FF61 (EUC-JP) A1 -> FF61 (Shift_JIS) A1 -> FF61 (windows-31j) A1 -> FF61 (x-SJIS_0213) # 全角の範囲外領域 1B24422120 -> FFFD (ISO-2022-JP) A1A0 -> FFFD (EUC-JP) 813F -> FFFD (Shift_JIS) 813F -> FFFD (windows-31j) 813F -> FFFD003F (x-SJIS_0213) # 全角の定義文字(参考) 1B24422121 -> 3000 (ISO-2022-JP) A1A1 -> 3000 (EUC-JP) 8140 -> 3000 (Shift_JIS) 8140 -> 3000 (windows-31j) 8140 -> 3000 (x-SJIS_0213) # 全角の未定義文字 1B24422271 -> FFFD (ISO-2022-JP) A2F1 -> FFFD (EUC-JP) 81EF -> FFFD (Shift_JIS) 81EF -> FFFD (windows-31j) 81EF -> 2194 (x-SJIS_0213) # 全角の未定義文字 1B24422321 -> FFFD (ISO-2022-JP) A3A1 -> FFFD (EUC-JP) 8240 -> FFFD (Shift_JIS) 8240 -> FFFD (windows-31j) 8240 -> 25B7 (x-SJIS_0213) # 全角の未定義文字 1B2442247E -> FFFD (ISO-2022-JP) A4FE -> FFFD (EUC-JP) 82FC -> FFFD (Shift_JIS) 82FC -> FFFD (windows-31j) 82FC -> FFFD (x-SJIS_0213)
# ISO-2022-JP の最初の変換はバグっぽいですが。
ところが、Java8 だと U+FFFD が1つだったり2つだったり、2バイト目が有効な文字だったら変換されたりと安定しない。
# 半角の未定義文字 1B284920 -> FF60 (ISO-2022-JP) 8EA0 -> FFFD (EUC-JP) A0 -> FFFD (Shift_JIS) A0 -> FFFD (windows-31j) A0 -> FFFD (x-SJIS_0213) # 半角の定義文字(参考) 1B284921 -> FF61 (ISO-2022-JP) 8EA1 -> FF61 (EUC-JP) A1 -> FF61 (Shift_JIS) A1 -> FF61 (windows-31j) A1 -> FF61 (x-SJIS_0213) # 全角の範囲外領域 1B24422120 -> FFFD (ISO-2022-JP) A1A0 -> FFFD (EUC-JP) 813F -> FFFD003F (Shift_JIS) 813F -> FFFD003F (windows-31j) 813F -> FFFD003F (x-SJIS_0213) # 全角の定義文字(参考) 1B24422121 -> 3000 (ISO-2022-JP) A1A1 -> 3000 (EUC-JP) 8140 -> 3000 (Shift_JIS) 8140 -> 3000 (windows-31j) 8140 -> 3000 (x-SJIS_0213) # 全角の未定義文字 1B24422271 -> FFFD (ISO-2022-JP) A2F1 -> FFFD (EUC-JP) 81EF -> FFFD (Shift_JIS) 81EF -> FFFD (windows-31j) 81EF -> 2194 (x-SJIS_0213) # 全角の未定義文字 1B24422321 -> FFFD (ISO-2022-JP) A3A1 -> FFFD (EUC-JP) 8240 -> FFFD0040 (Shift_JIS) 8240 -> FFFD0040 (windows-31j) 8240 -> 25B7 (x-SJIS_0213) # 全角の未定義文字 1B2442247E -> FFFD (ISO-2022-JP) A4FE -> FFFD (EUC-JP) 82FC -> FFFD (Shift_JIS) 82FC -> FFFDFFFD (windows-31j) 82FC -> FFFD (x-SJIS_0213)
未定義文字なので大きな問題にはならないかもしれないけれど、こういう非互換は勘弁してほしいなぁ…。
参考までに、チェックに使った環境とソースコードは以下の通り。
java version "1.8.0_60" Java(TM) SE Runtime Environment (build 1.8.0_60-b27) Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
java version "1.7.0_72" Java(TM) SE Runtime Environment (build 1.7.0_72-b14) Java HotSpot(TM) Client VM (build 24.72-b04, mixed mode)
import java.nio.charset.Charset; class CheckUndefined { static final Charset ISO_2022_JP = Charset.forName("ISO-2022-JP"); static final Charset EUC_JP = Charset.forName("EUC-JP"); static final Charset SHIFT_JIS = Charset.forName("Shift_JIS"); static final Charset WINDOWS_31J = Charset.forName("Windows-31J"); static final Charset SHIFT_JIS_2004 = Charset.forName("x-SJIS_0213"); public static void main(String[] args) { checkA0(); checkA1(); check813F(); check8140(); check81EF(); check8240(); check82FC(); } static void checkA0() { println("# 半角の未定義文字"); check(new byte[] {0x1B, 0x28, 0x49, 0x20}, ISO_2022_JP); check(new byte[] {(byte) 0x8E, (byte) 0xA0}, EUC_JP); check(new byte[] {(byte) 0xA0}, SHIFT_JIS); check(new byte[] {(byte) 0xA0}, WINDOWS_31J); check(new byte[] {(byte) 0xA0}, SHIFT_JIS_2004); } static void checkA1() { println("# 半角の定義文字(参考)"); check(new byte[] {0x1B, 0x28, 0x49, 0x21}, ISO_2022_JP); check(new byte[] {(byte) 0x8E, (byte) 0xA1}, EUC_JP); check(new byte[] {(byte) 0xA1}, SHIFT_JIS); check(new byte[] {(byte) 0xA1}, WINDOWS_31J); check(new byte[] {(byte) 0xA1}, SHIFT_JIS_2004); } static void check813F() { println("# 全角の範囲外領域"); check(new byte[] {0x1B, 0x24, 0x42, 0x21, 0x20}, ISO_2022_JP); check(new byte[] {(byte) 0xA1, (byte) 0xA0}, EUC_JP); check(new byte[] {(byte) 0x81, 0x3F}, SHIFT_JIS); check(new byte[] {(byte) 0x81, 0x3F}, WINDOWS_31J); check(new byte[] {(byte) 0x81, 0x3F}, SHIFT_JIS_2004); } static void check8140() { println("# 全角の定義文字(参考)"); check(new byte[] {0x1B, 0x24, 0x42, 0x21, 0x21}, ISO_2022_JP); check(new byte[] {(byte) 0xA1, (byte) 0xA1}, EUC_JP); check(new byte[] {(byte) 0x81, 0x40}, SHIFT_JIS); check(new byte[] {(byte) 0x81, 0x40}, WINDOWS_31J); check(new byte[] {(byte) 0x81, 0x40}, SHIFT_JIS_2004); } static void check81EF() { println("# 全角の未定義文字"); check(new byte[] {0x1B, 0x24, 0x42, 0x22, 0x71}, ISO_2022_JP); check(new byte[] {(byte) 0xA2, (byte) 0xF1}, EUC_JP); check(new byte[] {(byte) 0x81, (byte) 0xEF}, SHIFT_JIS); check(new byte[] {(byte) 0x81, (byte) 0xEF}, WINDOWS_31J); check(new byte[] {(byte) 0x81, (byte) 0xEF}, SHIFT_JIS_2004); } static void check8240() { println("# 全角の未定義文字"); check(new byte[] {0x1B, 0x24, 0x42, 0x23, 0x21}, ISO_2022_JP); check(new byte[] {(byte) 0xA3, (byte) 0xA1}, EUC_JP); check(new byte[] {(byte) 0x82, 0x40}, SHIFT_JIS); check(new byte[] {(byte) 0x82, 0x40}, WINDOWS_31J); check(new byte[] {(byte) 0x82, 0x40}, SHIFT_JIS_2004); } static void check82FC() { println("# 全角の未定義文字"); check(new byte[] {0x1B, 0x24, 0x42, 0x24, 0x7E}, ISO_2022_JP); check(new byte[] {(byte) 0xA4, (byte) 0xFE}, EUC_JP); check(new byte[] {(byte) 0x82, (byte) 0xFC}, SHIFT_JIS); check(new byte[] {(byte) 0x82, (byte) 0xFC}, WINDOWS_31J); check(new byte[] {(byte) 0x82, (byte) 0xFC}, SHIFT_JIS_2004); } static void check(byte[] bytes, Charset charset) { println(String.format("%s -> %s (%s)", toHexString(bytes), toHexString(new String(bytes, charset).toCharArray()), charset.toString())); } static String toHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte c : bytes) { sb.append(String.format("%02X", c)); } return sb.toString(); } static String toHexString(char[] chars) { StringBuilder sb = new StringBuilder(); for (char c : chars) { sb.append(String.format("%04X", (int) c)); } return sb.toString(); } static void println(String s) { System.out.println(s); } }