谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

いいから聞け! 俺が文字コードについて教えてやるよ その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、つまり「?」に相当する文字に変換されてしまいます。


さて、こうやって取得したバイト配列を、どうすれば文字列化できるのか。
文字コードには直接関係ないのですが、せっかくなので紹介しておきましょう。

  1. Integer.toHexStringを利用する
  2. String.formatを利用する
  3. 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つの方法があります。

  1. \uXXXX のUnicode文字表現で文字コードを指定して、文字や文字列を作成する。
  2. byte配列で文字コードを指定して、new Stringを使って文字列を作成する。
  3. 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つめの方法は、書き方は非常にシンプルですが
UnicodeUTF-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では文字をUTF-16で扱っていることが確認できました。

要するに、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の文字コードへのマッピングと言えるのです。


次回以降に説明する文字化け、文字コードチェックにおいても、
このマッピングの考え方は何度も登場するので、ぜひおさえておいてください。

まとめ


次回はいよいよ、Javaで日本語を扱う際にどうしても縁が切れない
「文字化け」について説明します。