CLOVER🍀

That was when it all began.

String、char、byteを使った文字コード関連のTips

自分がたまに使う割に、一部忘れたりするのでまとめてみることにしました。

文字コード系の調査とかトラブルがあった時に、知っておくと調査しやすいかも?といった内容です。

文字列(String)から、Unicodeのコードポイントを知りたい

Javaの内部的な文字の表現方法は、Unicode(正確にはUTF-16ですが…)です。なので、char1文字はサロゲートペアが登場しなければ、そのままUnicodeコードポイントを指します。

Stringをcharの配列に変換してから、文字列にしたりフォーマットしたりするとよいと思います。もちろん、いきなりcharから始めても構いませんが。

        String str = "あいうえおabcde";

        for (char c : str.toCharArray()) {
            System.out.println(c + " => 0x" + Integer.toHexString(c));
            System.out.printf("%s => %#x%n", c, (int)c);
            System.out.printf("%s => %#X%n", c, (int)c);
        }

このコードの実行結果。

あ => 0x3042
あ => 0x3042
あ => 0X3042
い => 0x3044
い => 0x3044
い => 0X3044
う => 0x3046
う => 0x3046
う => 0X3046
え => 0x3048
え => 0x3048
え => 0X3048
お => 0x304a
お => 0x304a
お => 0X304A
a => 0x61
a => 0x61
a => 0X61
b => 0x62
b => 0x62
b => 0X62
c => 0x63
c => 0x63
c => 0X63
d => 0x64
d => 0x64
d => 0X64
e => 0x65
e => 0x65
e => 0X65

System.out.printfの方は、%#xか%#Xかで結果が変わります。まあ、大文字になりますよ、と。

文字の特定のエンコーディングでのバイト値表現を知りたい

文字列または文字をいったんバイト配列に落として、そこからInteger.toHexStringで文字にして出力、という方法を自分はよく採ります。

今回は、文字エンコーディングとしては「UTF-8」を使っています。

        try {
            String str = "あいうえおabcde";
            for (char c : str.toCharArray()) {
                byte[] bytes =
                    new String(new char[] {c}).getBytes("UTF-8");
                System.out.print(c + "=> ");
                for (byte b : bytes) {
                    System.out.print("0x" + Integer.toHexString(b & 0xFF) + " ");
                }
                System.out.println();
            }
        } catch (java.io.UnsupportedEncodingException e) { }

Integer.toHexStringをかける時に、上位ビットを落としているところがポイントです。

実行結果。

あ=> 0xe3 0x81 0x82 
い=> 0xe3 0x81 0x84 
う=> 0xe3 0x81 0x86 
え=> 0xe3 0x81 0x88 
お=> 0xe3 0x81 0x8a 
a=> 0x61 
b=> 0x62 
c=> 0x63 
d=> 0x64 
e=> 0x65 

String#getBytesに指定する文字エンコーディングの値を変えると(例えば、「Shift_JIS」、「Windows-31J」など)、この結果も変わります。

Unicodeのコードポイントから、文字に変換したい

与えられる文字列中に基数が含まれているかどうかで、ちょっと変わります。まあ、10進数で与えられるなら、どちらでもいいと思いますが…。

ここでは、16進数で与えられると仮定します。

文字列自体に、「0x」のような基数表現が書かれている場合は、Integer.decodeを使います。

        String[] specHexUnicodeCodePoints = {
            "0x3042",  // 「あ」の16進数でのUnicode表現
            "0x3044",
            "0x3046",
            "0x3048",
            "0x304A",
            "0x61",  // 「a」の16進数でのUnicode表現
            "0x62",
            "0x63",
            "0x64",
            "0x65"
        };

        for (String s : specHexUnicodeCodePoints) {
            char c = (char)Integer.decode(s).intValue();
            System.out.printf("%s => %s%n", s, c);
        }

16進数であることがわかっているけれども、文字列中にそれが書かれていない場合は、Integer.parseIntの基数が指定できる方を使います。

        String[] hexUnicodeCodePoints = {
            "3042",  // 「あ」の16進数でのUnicode表現
            "3044",
            "3046",
            "3048",
            "304A",
            "61",  // 「a」の16進数でのUnicode表現
            "62",
            "63",
            "64",
            "65"
        };

        for (String s : hexUnicodeCodePoints) {
            char c = (char)Integer.parseInt(s, 16);
            System.out.printf("%s => %s%n", s, c);
        }

結果は、それぞれこうなります。

--- Integer.decode ---
0x3042 => あ
0x3044 => い
0x3046 => う
0x3048 => え
0x304A => お
0x61 => a
0x62 => b
0x63 => c
0x64 => d
0x65 => e

--- Integer.parseInt ---
3042 => あ
3044 => い
3046 => う
3048 => え
304A => お
61 => a
62 => b
63 => c
64 => d
65 => e

まあ、もともとの文字列に「0x」が入っているかどうかの差しかないわけですが…。

特定のエンコーディングでのバイト配列表現から文字列を作りたい

特定のエンコーディングでのバイト値を表したものから、文字列を作成します。

今回は、「UTF-8」でのバイト配列表現を例としました。

Stringクラスのコンストラクタを使用します。まあ、これは普通ですかね…。

        byte[][] bytes = {
            {(byte)0xE3, (byte)0x81, (byte)0x82},  // 「あ」のUTF-8でのバイト値表現
            {(byte)0xE3, (byte)0x81, (byte)0x84},
            {(byte)0xE3, (byte)0x81, (byte)0x86},
            {(byte)0xE3, (byte)0x81, (byte)0x88},
            {(byte)0xE3, (byte)0x81, (byte)0x8A},
            {(byte)0x61},  // 「a」のUTF-8でのバイト値表現
            {(byte)0x62},
            {(byte)0x63},
            {(byte)0x64},
            {(byte)0x65}
        };

        try {
            for (byte[] bs : bytes) {
                String s = new String(bs, "UTF-8");
                String byteString = "";
                for (byte b : bs)
                    byteString += String.format("0x%s ", Integer.toHexString(b & 0xFF));
                System.out.printf("%s=> %s%n", byteString, s);
            }
        } catch (java.io.UnsupportedEncodingException e) { }

実行結果。

0xe3 0x81 0x82 => あ
0xe3 0x81 0x84 => い
0xe3 0x81 0x86 => う
0xe3 0x81 0x88 => え
0xe3 0x81 0x8a => お
0x61 => a
0x62 => b
0x63 => c
0x64 => d
0x65 => e

文字が特定のエンコーディングの範囲内に収まっているかどうか知りたい

sun.ioパッケージ内のクラスを使用すると、文字(char)が特定のエンコーディングにマップ可能かどうか(要はバイト表現に変換できるか)を判定することができます。

もちろん、sun.ioパッケージなのでご利用には注意してくださいね。

ここでは、Shift_JISでよく問題になる機種依存文字をJISx0208、MS932(Windows-31J)、SJIS(Shift_JIS)の範囲で判定しています。

判定には、CharToByteXXXクラスのcanConvertメソッドを使用します。

        String s = "あ㈱1①";  // 機種依存文字入り
        sun.io.CharToByteJIS0208 jis0208 = new sun.io.CharToByteJIS0208();
        sun.io.CharToByteMS932 ms932 = new sun.io.CharToByteMS932();
        sun.io.CharToByteSJIS sjis = new sun.io.CharToByteSJIS();

        System.out.println("----- JISx0208判定 -----");
        for (char c : s.toCharArray()) {
            System.out.printf("%s => %b%n", c, jis0208.canConvert(c));
        }

        System.out.println("----- MS932(Windows-31J)判定 -----");
        for (char c : s.toCharArray()) {
            System.out.printf("%s => %b%n", c, ms932.canConvert(c));
        }

        System.out.println("----- SJIS(Shift_JIS)判定 -----");
        for (char c : s.toCharArray()) {
            System.out.printf("%s => %b%n", c, sjis.canConvert(c));
        }

※ 機種依存文字が表示できない環境向けに

        String s = "あ㈱1①";  // 機種依存文字入り

は、

        String s = "あ(株)1(1)";  // 機種依存文字入り

のような、機種依存文字の丸株と丸1です。

実行結果はこちら。

----- JISx0208判定 -----
あ => true
㈱ => false
1 => true
① => false
----- MS932(Windows-31J)判定 -----
あ => true
㈱ => true
1 => true
① => true
----- SJIS(Shift_JIS)判定 -----
あ => true
㈱ => false
1 => true
① => false

機種依存文字に対して結果がtrueになるのは、MS932だけですね。

これらのクラスは、以下のJARファイルに入っています。

$JAVA_HOME/jre/lib/charsets.jar

*UTF系はありませんよ

普段使っている文字エンコーディングに関するクラスがここにあるはずなので(名前は見つけにくいかもしれませんが)、知っていると役に立つことがあるかも?