いいから聞け! 俺が文字コードについて教えてやるよ その2(Javaの文字コード編)
前回のエントリーでは「Java関係ないやん」的なツッコミも受けましたが、
今回からは、Javaを前提にしていきます。
Javaで文字から文字コードを作る
新人くん「Javaで文字から文字コードを作りたいんですが、どうすれば良いんですか?」
先輩社員「getBytesすれば一発だね」
新人くん「getBytesした後のバイト配列から上手く文字列を作れないんですが・・・」
先輩社員「それは別の話だね」
前回のエントリーでは、WordやIMEを利用した文字と文字コードの変換方法を説明しましたが、
では、Javaではどうやれば良いのでしょうか。
Javaで文字から文字コードを作るには、
先輩社員も言っている通り、String#getBytes(charset)するだけです。
具体的なコードは、以下のようになります。
String str = "あいうえお"; byte[] utf8 = str.getBytes("UTF-8"); byte[] utf16 = str.getBytes("UTF-16"); byte[] utf32 = str.getBytes(Charset.forName("UTF-32")); byte[] win31 = str.getBytes(Charset.forName("Windows-31j"));
getBytesの引数には、文字列かCharsetクラスを渡すことができます。
前者なら検査例外、後者なら実行時例外がスローされますので
プロジェクトの性質や、期待する挙動などで、どちらを使うかを決めてください。
(ただしgetBytesにCharsetを渡せるのは、Java6以降です)
ちなみにCharsetの範囲にない文字をgetBytesした場合には、
常に 0x3F、つまり「?」に相当する文字に変換されてしまいます。
さて、こうやって取得したバイト配列を、どうすれば文字列化できるのか。
文字コードには直接関係ないのですが、せっかくなので紹介しておきましょう。
- Integer.toHexStringを利用する
- String.formatを利用する
- PrintStream.printfを利用する
具体的なコードは以下のようになります。
なお、bytesは(文字コードなどの)バイト配列、builderはStringBuilderのインスタンスとします。
for (byte b : bytes) { builder.append(Integer.toHexString((b & 0xF0) >> 4)); builder.append(Integer.toHexString(b & 0xF)); }
for (byte b : bytes) { builder.append(String.format("%02x", Byte.valueOf(b))); }
for (byte b : bytes) { System.out.printf("%02x", b); }
それぞれのコードの詳しい解説はしませんが、
String#formatと、Integer#toHexStringを比べると、
Integer#toHexStringのパフォーマンスの方が明らかに良いため(手元の計測で20倍程度)
業務処理などで利用する機会があれば、Integer#toHexStringを使うのが良いでしょう。
パフォーマンスを気にせず、ちょっと文字コードの確認をしてみるという用途では、
素早く書けるformatやprintfを使うと良いですね。
その場合は「%02x」だけ暗記しておけば、何とかなるでしょう。
Javaで文字コードから文字を作る
新人くん「逆にJavaで文字コードから文字を作りたいんですが・・・?」
先輩社員「new Stringするとか、色々あるよ」
新人くん「色々ですか?」
先輩社員「色々だよ」
Javaで文字コードから文字を作るには、大きく分けて3つの方法があります。
- \uXXXX のUnicode文字表現で文字コードを指定して、文字や文字列を作成する。
- byte配列で文字コードを指定して、new Stringを使って文字列を作成する。
- Stringで文字コードを指定して、文字に変換する。
具体的なコードは、以下のようになります。
String str = "\u3042\u3044";
byte[] bytes = { 0x30, 0x42, 0x30, 0x44 }; String str = new String(bytes, "UTF-16"); byte[] bytes = { (byte) 0x82, (byte) 0xA0, (byte) 0x82, (byte) 0xA2 }; String str = new String(bytes, "Windows-31j");
char c1 = (char) Integer.parseInt("3042", 16); char c2 = (char) Integer.decode("0x3044").intValue(); // char c2 = (char) Integer.decode("#3044").intValue(); でもOK String str = "" + c1 + c2;
いずれの方法でも "あい" という文字列ができあがります。
1つめの方法は、書き方は非常にシンプルですが
Unicode(UTF-16)の値しか指定できません。
2つめの方法は、どのようなCharsetでも指定できますが、
少し書き方が冗長になってしまいます。
3つめの方法は、あまり頻繁に行う方法ではないでしょうけど、
たとえば文字コード一覧のテキストファイルなどを読み込み、
そこから文字や文字列を作成するような処理を行う場合には有用です。
Javaの文字列は、本当にUnicodeなの?
新人くん「Javaって内部でUnicodeを使っているって聞いたんですが、本当ですか?」
先輩社員「あぁ、UTF-16でエンコードされたUnicodeを使っているんだよ」
新人くん「本当ですか? どうすればUTF-16を使っているって確認できるんですか?」
先輩社員「おっと、そろそろ会議の時間だ」
Javaが内部でどのような文字コードを利用しているのか、
普段プログラミングする上では、さほど意識する必要はありません。
そのため、「Javaの文字列はUnicodeだ」と聞いたことがあっても
実際にその目で確かめられた方は、あまり多くないのではないでしょうか。
Javaの文字列(java.lang.Stringクラス)は、内部でchar配列を保持していますので
要するにcharがUnicode(UTF-16)の文字コードを利用しているということになります。
本当にそうなのか?
以下のコードで簡単に確かめられます。
char c = 'あ'; System.out.printf("%02x", (int)c);
これで、出力結果は「3042」となりました。
つまりJavaの実行時には、
charは「あ」という文字を16進数の「3042」という値して保持していると言えます。
3042が、「あ」という文字のUTF-16コードであることは、以下のサイトなどで確認できます。
http://ash.jp/code/unitbl21.htm
http://www.fileformat.info/info/unicode/char/3042/index.htm
要するに、Javaの文字コード変換とは。
ここまでJavaの文字コード変換や、Javaの文字がUTF-16であることを説明してきました。
ではここで、そもそも「文字コード変換」とは何なのか、考えてみましょう。
まず、文字 → 文字コードの変換です。
byte[] bytes = str.getByte("Windows-31j");
この変換は、要するに char → byte[] へのマッピング、
つまり「UTF-16の文字コード → Windows-31jの文字コードへのマッピング」と言えます。
一方、文字コード → 文字の変換はどうでしょうか。
String str = new String(bytes, "Windows-31j");
この変換は逆に
「Windows-31jの文字コード → UTF-16の文字コードへのマッピング」になります。
厳密に言えば、やや正確でない捉え方かも知れませんが、
Javaにおける文字⇔文字コード変換とは、
UTF-16の文字コードと、各Charsetの文字コードへのマッピングと言えるのです。
次回以降に説明する文字化け、文字コードチェックにおいても、
このマッピングの考え方は何度も登場するので、ぜひおさえておいてください。