「Javaにおける文字コードまわりの話 - あしのあしあと」は、もう少しブラッシュアップしたい。その前に、検証用のプログラムを少しだけ整理しておきたい。
ここでは、次のような用語を用いることにする。
- 文字と文字の識別子の集合を「文字集合」と呼び、文字の識別子を「コードポイント」と呼ぶ。
- コードポイントからバイト列(バイト配列)へ変換する処理を「エンコード」と呼び、その逆を「デコード」と呼ぶ。
- エンコード、デコードの方法を「エンコード方式」や「文字エンコーディング」と呼ぶ。
Javaでは、文字集合と文字エンコーディングを組み合わせた「エンコーディングセット」という概念が用いられる。「エンコーディングセット」って用語、正直、あまり使わない*1。。
http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/intl/encoding.doc.html
サポートされているエンコーディングセット名の一覧は、次のようにして出力できる。
/** * 現在のJava仮想マシンでサポートされているエンコーディングセット名を出力する。 */ public static void printSupportedCharSet() { println(String.format("この環境のデフォルトエンコーディングセットは%sです。", Charset.defaultCharset().name())); println("この環境でサポートしているエンコーディングセットは次です。"); println("書式: java.nio API用の正準名 ( 別名... )"); Map<String, Charset> charSetMap = Charset.availableCharsets(); for (Map.Entry<String, Charset> entry : charSetMap.entrySet()) { print(String.format("%s ( ", entry.getKey())); for (String alias : entry.getValue().aliases()) { print(alias + " "); } println(")"); } } private static void println(Object obj) { System.out.println(obj); } private static void print(Object obj) { System.out.print(obj); }
さて、Javaの内部では、文字は、Unicodeを用いて扱われている。Java6では、BMP(基本多言語面)以外の文字にも対応しているが、今回もBMPの文字のみの話とする。
char型は、int型にキャストできる*2。この整数値が、Unicodeのコードポイントそのものだったりする。
int ch = (int) 'あ';
この場合であれば、次でも同じ。
char[] chars = {'あ'}; int ch = Character.codePointAt(chars, 0);
この時chの値は12354(0x3042)。ちょっとダサいが、これを16進数で表示するプログラムを書いてみた。
/** * 例えば、引数の文字として{@code a} を指定した場合には、{@code U+0061} が返される。 * @param ch BMP(基本多言語面)に含まれる文字 * @return 指定された文字のUnicodeコードポイント文字列 */ public static String getUnicodeCodePointString(char ch) { if (!Character.isDefined(ch)) throw new IllegalArgumentException( String.format( "BMP(基本多言語面)に含まれていない文字 %s(%s) が指定されました。", ch, (int) ch)); String hexCp = Integer.toHexString((int) ch); // 以下で、桁数を BMP_HEX_LENGTH 桁にそろえる。また、先頭に U+ を付加する。 // 例えば、hexCp が 61 の場合に U+0061 にする。 final int BMP_HEX_LENGTH = 4; if (BMP_HEX_LENGTH < hexCp.length()) // スローされないはず。 throw new RuntimeException(String.format( "文字 %s の16進数表現 %s が、%s 桁以内になっていません。", ch, hexCp, BMP_HEX_LENGTH)); StringBuilder sb = new StringBuilder(); while (sb.length() + hexCp.length() < BMP_HEX_LENGTH) { sb.append('0'); } sb.append(hexCp.toUpperCase()); return "U+" + sb.toString(); }
これで、Javaの内部で扱われている文字が、どのUnicodeのコードポイントにマッピングされているのか、調査することができる。
では、エンコーディングセットに含まれるすべての文字を取得しよう。文字(コードポイントの値)をインクリメントしていって、CharsetEncoder#canEncodeメソッドでエンコードできる文字を追加しているだけ。ちなみにメソッド中で、CharsetやCharsetEncoderを構築しているので、何度も呼ばないこと。TreeSetを用いているのは、表示のため。検索するならHashSetを用いること。
/** コードポイント順にソートするコンパレータ */ private static final Comparator<Character> CHAR_VALUE_COMPARATOR = new Comparator<Character>() { @Override public int compare(Character ch1, Character ch2) { return ch1.charValue() - ch2.charValue(); } }; /** * @param encordingsetName エンコーディングセット名 * @return 指定されたエンコーディングセット名に含まれるすべての文字 */ public static Set<Character> getAllChars(String encordingsetName) { Charset charset = Charset.forName(encordingsetName); CharsetEncoder encoder = charset.newEncoder(); Set<Character> chars = new TreeSet<Character>(CHAR_VALUE_COMPARATOR); char ch = Character.MIN_VALUE; for (; ch < Character.MAX_VALUE; ch++) { if (encoder.canEncode(ch)) { chars.add(Character.valueOf(ch)); } } return chars; }
余談だが、java.nioのエンコーダやデコーダは、次のように使える。これもCharset、CharsetEncoder、CharsetDecoderを毎回構築しているし、CharBufferやByteBufferを毎回確保しているので、調査目的以外では用いないこと。
/** * @param encordingSetName エンコーディングセット名 * @param chars エンコード対象の文字配列 * @return 指定された文字配列をエンコードしたバイト配列 * @throws CharacterCodingException エンコードに失敗した場合にスローされる * @see CharsetEncoder#encode(CharBuffer) */ public static byte[] encordChars(String encordingSetName, char... chars) throws CharacterCodingException { Charset charset = Charset.forName(encordingSetName); CharsetEncoder encoder = charset.newEncoder(); CharBuffer in = CharBuffer.wrap(chars); ByteBuffer out = encoder.encode(in); return out.array(); } /** * @param encordingSetName エンコーディングセット名 * @param bytes デコード対象のバイト配列 * @return 指定されたバイト配列をデコードした文字配列 * @throws CharacterCodingException デコードに失敗した場合にスローされる * @see CharsetDecoder#decode(ByteBuffer) */ public static char[] decodeBytes(String encordingSetName, byte... bytes) throws CharacterCodingException { Charset charset = Charset.forName(encordingSetName); CharsetDecoder decoder = charset.newDecoder(); ByteBuffer in = ByteBuffer.wrap(bytes); CharBuffer out = decoder.decode(in); return out.array(); }
ここまでで、次のことができるようになった。
今日はここまで。続きはいつできることやら。。
2010/07/24追記
「Javaにおける文字コードまわりの話(5)」に、(整理し切れてはいないのだが)簡単なまとめを書いた。