2010年05月05日

JavaでBase64について

うわ、ごめん。前回Javaアプレット版5つの宝島について書くっていってたのに、1ヵ月も間あいちゃった。もちろんその間さっぱり活動してなかったわけじゃないんだけど、どうも配布Wikiの方に力注いじゃって・・・
 
今回は予告通り、前回の続き、Base64の実装について書くよ!前回、セーブデータを文字列化するためにBase64符号化しよう、というところまでは決まったんだけど、Jakarta Commonsをアプレットで使用するには署名済みjarを作らなきゃいけいないのでメンドクセー、ソースコード書いて自力で符号化してえなあというところまで語ったはず。今回は実装編だ!
 
 
どっかから拾ってきたエンコード


正直に告白します。base64エンコードのロジックはどこかから拾ってきたはずなんですが、1ヵ月も前の話なんでどこから拾ってきたかすっかり忘れました。たしかWebページには自由に使ってくださいって書いてたはずだから問題ないと思うんだけど、もし問題あったらごめんなさい。

では以下にエンコードのソースを貼りつけます。

private static final String[] base64table = {
     "A", "B", "C", "D", "E", "F", "G", "H",
     "I", "J", "K", "L", "M", "N", "O", "P",
     "Q", "R", "S", "T", "U", "V", "W", "X",
     "Y", "Z", "a", "b", "c", "d", "e", "f",
     "g", "h", "i", "j", "k", "l", "m", "n",
     "o", "p", "q", "r", "s", "t", "u", "v",
     "w", "x", "y", "z", "0", "1", "2", "3",
     "4", "5", "6", "7", "8", "9", "+", "/"
};

public static String encode( byte[] bytes ) {

    // バイト配列をビットパターンに変換します。
    StringBuffer bitPattern = new StringBuffer();
    for ( int i = 0; i < bytes.length; ++i ) {
        int b = bytes[i];
        if ( b < 0 ) {
            b += 256;
        }
        String tmp = Integer.toBinaryString( b );
        while ( tmp.length() < 8 ) {
            tmp = "0" + tmp;
        }
        bitPattern.append( tmp );
    }

    // ビットパターンのビット数が6の倍数にするため、末尾に0を追加します。
    while ( bitPattern.length() % 6 != 0 ) {
        bitPattern.append( "0" );
    }

    // 変換表を利用して、ビットパターンを4ビットずつ文字に変換します。
    StringBuffer encoded = new StringBuffer();
    for ( int i = 0; i < bitPattern.length(); i += 6 ) {
        String tmp = bitPattern.substring( i, i + 6 );
        int index = Integer.parseInt( tmp, 2 );
        encoded.append( base64table[index] );
    }

    // 変換後の文字数を4の倍数にするため、末尾に=を追加します。
    while ( encoded.length() % 4 != 0 ) {
        encoded.append( "=" );
    }

    return encoded.toString();
}

変換表を変数として別出ししているほかは、1メソッドで完結している。

アルゴリズム全般は世の中に山ほど溢れているCのソースと同じことをしているので、だいたいそれが参考になるけどJava特有のクセの部分を少し解説

b += 256;

エンコードメソッドの序盤で見つかる謎の256加算部分。これはJavaのbyte型が何故か符号付きの型なもので、128以上の数がマイナスになってしまうのを補正するものだ。なぜにJavaのbyte型にマイナスなんてあるのかいまいち理解できないけど、減算の時にほかの型と同じように0未満の数を処理する必要があるからなんだろうか???

String tmp = Integer.toBinaryString( b );

また、JavaはCと比べてビット演算の機能が少ないため、↓のような操作ができない

01100101 01100001 11110010 8ビットずつ区切りの数値を・・・
011001 010110 000111 110010 6ビットずつに区切る
そこでどうするかというと、青字のように文字列に一度変換して文字列操作をする、というなんだかかゆいところに手が届かない処理をしている。でもきっとJavaではこれ以上スマートな解決法ないんだろうなあ。

複合するよ!

一方、デコードの方はそこまで難しい工夫は必要ない。

public static byte[] decode(String str) {

    StringBuffer bitPattern = new StringBuffer();
    for (char c : str.toCharArray()) {
        int code = -1;
        String letter = Character.toString(c);
        for (int i = 0; i < base64table.length; i++) {
            if (base64table[i].equals(letter)) {
                code = i;
                break;
            }
        }
        if (code >= 0) {
            String tmp = Integer.toBinaryString(code);
            while (tmp.length() < 6) {
                tmp = "0" + tmp;
            }
            bitPattern.append(tmp);
        }
    }

    ByteArrayOutputStream decoded = new ByteArrayOutputStream();
    for (int i = 0; i + 7 < bitPattern.length(); i+=8) {
        String tmp = bitPattern.substring(i, i + 8);
        decoded.write(Integer.parseInt(tmp, 2));
    }

    return decoded.toByteArray();
}
特に書くべきところは緑字のところくらいかな。このif文で符号化テーブルに存在しない文字は処理しないようにしてる。符号化テーブルに存在しない文字って何やねんという話だけど、Base64で埋め合わせ文字として使われている「=」とか、あとBase64本来の規格では80文字ごとに挿入される可能性のある改行文字とか。そこら辺はif文で処理しないようになっている。

ほかは素直に規格どおり、符号を復号化してByteArrayOutputStreamに突っ込んでいるだけ。シンプルだね!

今回のソースは↓の添付

Base64.java

に保存しておきました。前段で紹介したとおりパクリ元の問題がなければbsdライセンスで配布するよ!好きに使って!

posted by LoyalTouch at 16:09| Comment(0) | TrackBack(0) | 5つの宝島 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

この記事へのトラックバックURL
http://blog.seesaa.jp/tb/148824796

この記事へのトラックバック