連載
» 2007年06月21日 00時00分 公開

プログラマーの常識をJavaで身につける(6):‘愛’で学ぶ文字コードと文字化けの常識 (2/4)

[伊賀敏樹,NTTデータ ビジネスブレインズ]

Javaで文字エンコーディングを使ってみよう

 前述のように、Javaは内部的に文字エンコーディングとしてUTF-16を用いています。このUTF-16をほかの文字エンコーディングに変換するための機能がJava APIとして提供されています。世の中にあるさまざまな文字エンコーディングを取り扱うために、この機能は必要になります。

バイト配列を16進表記文字列にする便利クラスを差し上げます

 まず、バイト配列を一般的な16進表記文字列にするためのユーティリティクラス(HexStringUtil.java)を準備しておきます。

public final class HexStringUtil {

    /**
    * 与えられたバイト配列を16進表記の文字列に変換します。
    * 2バイト目以降には、前のバイトとの区切りのために
    * 半角空白を付与します。
    * 変換例。入力:[愛植岡]のシフトJIS化バイト配列
    * → 出力:[88 a4 90 41 89 aa]
    *
    * @param argBytes バイト配列。
    * @return 16進表記の文字列。
    */
    public static String bytesToHexString(final byte[] argBytes) {

        final StringBuffer buffer = new StringBuffer();
        for(int byteIndex=0;byteIndex<argBytes.length;byteIndex++){
            if (byteIndex != 0) {
                //2バイト目以降には半角空白を区切り文字として付与
                buffer.append(' ');
            }
            buffer.append(byteToHexString(argBytes[byteIndex]));
        }
        return buffer.toString();
    }
    /**
    * 与えられたバイトを16進表記の文字列に変換します。
    * 必ず2文字が戻ります。
    *
    * @param argByte バイト値。
    * @return 16進表記の文字列。
    */
    public static String byteToHexString(final byte argByte) {
        int wrkValue = argByte;
        if (wrkValue < 0) {
            // 負の値の場合には正の値に変換します。
            wrkValue += 0x100;
        }
        String result = Integer.toHexString(wrkValue);
        if (result.length() < 2) {
            //1文字の場合には0の文字を加えて2文字になるようにします
            result = "0" + result;
        }
        return result;
    }
}

 これ以降のソースコードは、このユーティリティクラスが存在することを前提としています。なお、このようなユーティリティは文字コードを調べる目的以外でも有益である場合があります。ぜひご活用ください。

文字列 → シフトJISのバイト配列

 最初に、日本語の文字エンコーディングとしてよく用いられているシフトJISの文字エンコーディングを調べます。Java上の文字列をシフトJISのバイト配列に変換する例を見てみましょう。

import java.io.UnsupportedEncodingException;

// 文字列をバイト配列に変換するサンプル
public class StringToBytesSample {
    public static void main(final String[] args) {
        try {
            new StringToBytesSample().process("愛植岡");
        } catch (UnsupportedEncodingException ex) {
            System.out.println("サポートされないエンコーディング["
                + ex.getMessage() + "]が指定されました。");
            ex.printStackTrace();
        }
    }
    /**
    * シフトJIS化したバイト配列を表示
    * @param argTargetString 処理したい文字列
    * @throws UnsupportedEncodingException
    * エンコーディングがサポートされない場合
    */
    public void process(final String argTargetString)
    throws UnsupportedEncodingException {
        System.out.println("[" + argTargetString
            + "]をシフトJISのバイト配列に変換");
        final byte[] result =argTargetString.getBytes("Shift_JIS");
        System.out.println(HexStringUtil.bytesToHexString(result));
    }
}

 実行すると、下記のような結果を取得できます。

実行結果

[愛植岡]をシフトJISのバイト配列に変換

88 a4 90 41 89 aa


 この例では3文字が6個のbyteへと変換されていることが分かります。ただこれでは、どの文字がどの文字に変換されたのか分からないので、1文字だけ変換してみます。上記ソースコード中の桃色の個所を下記のように書き換えます。

            new StringToBytesSample().process("愛");

 すると、‘愛’の文字が88 a4 という2バイトへと変換されることが分かります。

実行結果

[愛]をシフトJISのバイト配列に変換

88 a4


シフトJISのバイト配列 → 文字列

 これとは逆に、バイト配列から文字列を作ることができます。今回得られたバイト配列を下記ソースコード中の桃色の個所のように与えて、文字列へと変換します。

import java.io.UnsupportedEncodingException;

// バイト配列を文字列に変換するサンプル
public class BytesToStringSample {
    public static void main(final String[] args) {
        try {
            final byte[] aiueoka = new byte[] {
                (byte) 0x88, (byte) 0xa4, (byte) 0x90,
                (byte) 0x41, (byte) 0x89, (byte) 0xaa };

            new BytesToStringSample().process(aiueoka);
        } catch (UnsupportedEncodingException ex) {
            System.out.println("サポートされないエンコーディング["
                + ex.getMessage() + "]が指定されました。");
            ex.printStackTrace();
        }
    }
    /**
    * シフトJIS化としてバイト配列を文字列表示
    * @param argTargetBytes
    * @throws UnsupportedEncodingException
    */
    public void process(final byte[] argTargetBytes)
    throws UnsupportedEncodingException {
        System.out.println("["
            + HexStringUtil.bytesToHexString(argTargetBytes)
            + "]をシフトJISのバイト配列として文字列に変換");
        final String result=new String(argTargetBytes,"Shift_JIS");
        System.out.println(result);
    }
}

 実行結果は下記のようになります。元の文字列に戻っていることが分かります。

実行結果

[88 a4 90 41 89 aa]をシフトJISのバイト配列として文字列に変換

愛植岡


 文字列とバイト配列の相互変換は、下記のような対応になります。

図3 文字列とバイト配列の相互変換 図3 文字列とバイト配列の相互変換

それ以外の文字エンコーディングを調べるための事前準備

 それでは、シフトJIS以外の文字エンコーディングも調べていきます。まず、簡単に文字エンコーディングを切り替えられるように、先ほどのStringToBytesSample.javaのソースコードを少し拡張し、文字エンコーディング指定を外部から変更できるようにしておきます。

import java.io.UnsupportedEncodingException;

public class StringToBytesSampleEnc {
    public static void main(final String[] args) {
        try {
            new StringToBytesSampleEnc().process("愛植岡"
                , "Shift_JIS");
        } catch (UnsupportedEncodingException ex) {
            System.out.println("サポートされないエンコーディング["
                + ex.getMessage()+ "]が指定されました。");
            ex.printStackTrace();
        }
    }
    public void process(final String argTargetString
                      , final String argEncoding)
    throws UnsupportedEncodingException {
        System.out.println("[" + argTargetString + "]を["
            + argEncoding + "]バイト配列に変換");
        final byte[] result =argTargetString.getBytes(argEncoding);
        System.out.println(HexStringUtil.bytesToHexString(result));
    }
}

 準備ができたので、サンプルプログラムを動作させてみましょう。私たちはプログラマーなので、こういった事象をプログラムを使って検証できます。

EUC-JPエンコーディング

 まず、UNIXなどで日本語を扱う際によく使われているEUC-JPエンコーディングを指定して動作させてみます。

        new StringToBytesSampleEnc().process("愛植岡", "EUC-JP");

実行結果

[愛植岡]を[EUC-JP]バイト配列に変換

b0 a6 bf a2 b2 ac


 ここで指定できる文字列は 前述のサポートされているエンコーディングのページで調べることができます。EUC-JP以外にも、よく知られる文字エンコーディングを与えてみましょう。

ISO-2022-JPエンコーディングを指定した場合

実行結果

[愛植岡]を[ISO-2022-JP]バイト配列に変換

1b 24 42 30 26 3f 22 32 2c 1b 28 42


 エスケープシーケンスと呼ばれるものが付与されているため、かなりバイト数が増えます。エスケープシーケンスについての詳細は、『文字コード超研究』の316ページなどを参照ください。

UTF-8エンコーディングを指定した場合

 特に、XMLなどにおいて頻繁に利用されるのが、UTF-8エンコーディングです。UTF-8はUnicodeの文字エンコーディングのひとつです。

実行結果

[愛植岡]を[UTF-8]バイト配列に変換

e6 84 9b e6 a4 8d e5 b2 a1


UTF-16エンコーディングを指定した場合

 同じく、XMLなどにおいてよく利用されるのが、UTF-16エンコーディングです。UTF-16はUnicodeの文字エンコーディングのひとつです。

 UTF-16エンコーディングはバイト配列表現に変換する際に注意が必要になります。というのも、16ビットであるchar型を8ビットであるbyte型に詰め込む際に、上位8ビットと下位8ビットのいずれのbyteを配列として先に位置させるのか(エンディアン)を明示する必要があるからです。

 UTF-16BEビッグエンディアン(big endian)を明示したUTF-16です。上位8ビットの方が先に配置されます(先ほど‘愛’は611bだったことを思い出してください)。

実行結果

[愛植岡]を[UTF-16BE]バイト配列に変換

61 1b 69 0d 5c a1


 UTF-16LEリトルエンディアン(little endian)を明示したUTF-16です。下位8ビットの方が先に配置されます。

実行結果

[愛植岡]を[UTF-16LE]バイト配列に変換

1b 61 0d 69 a1 5c


 UTF-16はエンディアンを明示していません。その代わり、先頭に「バイト順マークBOMByte Order Mark)」というものを2バイト付与してエンディアンを表現します。この2バイトでリトルエンディアンかビッグエンディアンかを判断できるようになっています。

実行結果

[愛植岡]を[UTF-16]バイト配列に変換

fe ff 61 1b 69 0d 5c a1


 なお、この実行例はビッグエンディアンです。リトルエンディアンの際には、先頭にff fe の2バイトが付与されます。

サポートされていない文字エンコーディングを指定すると例外発生

 ここでもし、Javaでサポートされていない文字エンコーディングを指定すると、例外が発生します。例えば、存在しない名称の“ARIMASEN”エンコーディングを指定してみて動作させてください。下記のような例外が発生します。

実行結果

[愛植岡]を[ARIMASEN]バイト配列に変換

サポートされないエンコーディング[ARIMASEN]が指定されました。

java.io.UnsupportedEncodingException: ARIMASEN

at sun.io.Converters.getConverterClass(Unknown Source)

at sun.io.Converters.newConverter(Unknown Source)

at sun.io.CharToByteConverter.getConverter(Unknown Source)

at java.lang.StringCoding.encode(Unknown Source)

at java.lang.String.getBytes(Unknown Source)

at StringToBytesSampleEnc.process(StringToBytesSampleEnc.java:18)

at StringToBytesSampleEnc.main(StringToBytesSampleEnc.java:6)


 このように、文字エンコーディング名は少しでも間違えると、サポートしないエンコーディング名を指定したことになり、例外が発生します。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。