argius note

プログラミング関連

シリアライズデータとテキストデータの相互変換

シリアライズのデータを制御文字を含まない文字列に変換し、その文字列からオブジェクトを復元できるようにしたい。つまり、ファイルに保存したときにバイナリファイルにならないようにしたい。J2SE1.4では、直列化をXMLで行う標準APIがあるが、J2SE1.3でやりたい。
Jakartaのライブラリを漁れば何か見つかると思うが、プログラム本体より大きいライブラリを提供パッケージに添付するのは、あまりやりたくない。ライセンスが難しいものならなおさらだ。
そんなわけで、性懲りも無く「再発明」。
(IOExceptionは実行時エラーとみなす。)

import java.io.*;

/**
 * 制御文字を含まない文字列による直列化(シリアライズ)を行う。
 * 
 * 直列化情報をASCIIのうち制御文字でないもの(0x20〜0x7E)だけで
 * 構成される文字列に変換、またはその逆を行う。
 * 制御文字は1バイトごとに、バイト値の16進数表記文字列を
 * <code>[</code>と<code>]</code>で囲んだ4バイトに変換される。
 * @see java.io.Serializable
 * @see java.io.ObjectInputStream
 * @see java.io.ObjectOutputStream
 */
public final class StringSerializer {

    private static final char S = '[';
    private static final char E = ']';

    /**
     * 直列化する。
     * @param object 直列化するオブジェクト
     * @return 直列化された文字列
     */
    public static String serialize(Object object) {
        ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
        try {
            ObjectOutputStream os = new ObjectOutputStream(byteArray);
            try {
                os.writeObject(object);
            } finally {
                os.close();
            }
        } catch (IOException ex) {
            throw new RuntimeException(ex.toString());
        }
        byte[] bytes = byteArray.toByteArray();
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            int a = bytes[i] & 0xFF;
            if (a < ' ' || a > '~' || a == S || a == E) {
                buffer.append(S);
                buffer.append(toHexString(bytes[i]));
                buffer.append(E);
            } else {
                buffer.append((char)a);
            }
        }
        return buffer.toString();
    }

    /**
     * 直列化された文字列からオブジェクトを復元する。
     * @param string 直列化された文字列
     * @return 復元されたオブジェクト
     * @throws ClassNotFoundException 復元したオブジェクトのクラスが見つからない場合
     */
    public static Object deserialize(String string) throws ClassNotFoundException {
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            try {
                for (int i = 0, n = string.length(); i < n; i++) {
                    char c = (char)string.charAt(i);
                    if (c == S) {
                        String hex = string.substring(i + 1, i + 3);
                        os.write(Integer.parseInt(hex, 16));
                        i += 3;
                    } else {
                        os.write(c);
                    }
                }
            } finally {
                os.close();
            }
            ObjectInputStream is = new ObjectInputStream(new ByteArrayInputStream(os.toByteArray()));
            try {
                return is.readObject();
            } finally {
                is.close();
            }
        } catch (IOException ex) {
            throw new RuntimeException(ex.toString());
        }
    }

    /**
     * バイト値を16進数表記にする。
     * @param b バイト値
     * @return 16進数表記
     */
    private static String toHexString(byte b) {
        final String chars = "0123456789ABCDEF";
        int lo = b & 0xF;
        int hi = b >> 4 & 0xF;
        return new String(new char[]{chars.charAt(hi), chars.charAt(lo)});
    }

}

使い方は以下の通り。

List list = new ArrayList();
list.add("hello");
list.add("good bye");
System.out.println(list.getClass());
System.out.println(list);
String serialized = StringSerializer.serialize(list);
System.out.println("---");
System.out.println(serialized);
try {
    Object deserialized = StringSerializer.deserialize(serialized);
    System.out.println("---");
    System.out.println(deserialized.getClass());
    System.out.println(deserialized);
} catch (ClassNotFoundException ex) {
    ex.printStackTrace();
}

結果は以下の通り。(一部改行しています。)

class java.util.ArrayList
[hello, good bye]
---
[AC][ED][00][05]sr[00][13]java.util.ArrayListx[81][D2]
[1D][99][C7]a[9D][03][00][01]I[00][04]sizexp[00][00][00][02]
w[04][00][00][00][0A]t[00][05]hellot[00][08]good byex
---
class java.util.ArrayList
[hello, good bye]

XMLの場合のように、構造を知ることは難しいが、Stringフィールドの値くらいはちゃんと読めることと、値が記号に埋もれないようにしている。