如何将对象进行二进制序列化/反序列化成字符串?

13

我需要将对象序列化为字符串并进行反序列化。

我在Stackoverflow上阅读了建议,并编写了以下代码:

class Data implements Serializable {
int x = 5;
int y = 3;   
}

public class Test {
public static void main(String[] args) {

    Data data = new Data();

    String out;

    try {
        // zapis
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);

        oos.writeObject(data);

        out = new String(baos.toByteArray());
        System.out.println(out);

        // odczyt.==========================================

        ByteArrayInputStream bais = new ByteArrayInputStream(out.getBytes());

        ObjectInputStream ois = new ObjectInputStream(bais);

        Data d = (Data) ois.readObject();

        System.out.println("d.x = " + d.x);
        System.out.println("d.y = " + d.y);

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

}

}

但是我遇到了错误:

java.io.StreamCorruptedException: invalid stream header: EFBFBDEF
at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:801)
at java.io.ObjectInputStream.<init>(ObjectInputStream.java:298)
at p.Test.main(Test.java:37)

为什么? 我期望: d.x = 5 d.y = 3

怎样才能以良好的方式实现? 啊,我不想把这个对象写入文件。 我必须将它保存为字符串格式。


1
你为什么要在字符串中存储二进制表示,而不是将其保留为字节数组或其他形式?这样做的确切原因是什么?... - Costi Ciudatu
@CostiCiudatu 因为我需要编写一个将对象存储到 SQLite 数据库中的方法,但是 SQLite 的一部分不受我的控制。而且还有一个文本列。现在我使用 XML 序列化,但速度很慢。我需要更快的方法。 - LunaVulpo
你是否检查过SQLite是否支持类似BLOB的方式来存储原始字节? - Costi Ciudatu
@CostiCiudatu Sqlite支持BLOB,但它如何帮助我呢? - LunaVulpo
2个回答

11

将数据转换为字符串并不完全是损坏数据。将其转换为“UTF-8”会造成损害,因为它不是双射的(一些字符是2个字节,但并非所有2个字节序列都允许作为字符序列),而“ISO-8859-1”是双射的(一个字符串的一个字符是一个字节,反之亦然)。

与此相比,Base64编码并不是非常节省空间。

这就是为什么我建议:

/**
 * Serialize any object
 * @param obj
 * @return
 */
public static String serialize(Object obj) {
    try {
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream so = new ObjectOutputStream(bo);
        so.writeObject(obj);
        so.flush();
        // This encoding induces a bijection between byte[] and String (unlike UTF-8)
        return bo.toString("ISO-8859-1");
    } catch (Exception e) {
        e.printStackTrace();
    }
}
/**
 * Deserialize any object
 * @param str
 * @param cls
 * @return
 */
public static <T> T deserialize(String str, Class<T> cls) {
    // deserialize the object
    try {
        // This encoding induces a bijection between byte[] and String (unlike UTF-8)
        byte b[] = str.getBytes("ISO-8859-1"); 
        ByteArrayInputStream bi = new ByteArrayInputStream(b);
        ObjectInputStream si = new ObjectInputStream(bi);
        return cls.cast(si.readObject());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

你如何确保二进制表示可以转换为“String”?例如,String终止字符(通常在C中为\0)如何转换为String内的有效字符?答案是,你无法确定。因此,我们应该将字节数组转换为安全的文本表示,例如Base64。 - Ron Klein

11

使用
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 而不是 ByteArrayInputStream bais = new ByteArrayInputStream(out.getBytes());,因为字符串转换会损坏数据(由于编码问题)。

如果你确实需要把结果存储在字符串中,你需要一种安全的方法来存储任意字节到一个字符串中。其中一种方法是使用Base64编码。

另一种完全不同的方法是不使用标准Java序列化此类,而是创建自己的数据到/从字符串转换器。


1
+1 不要忘记关闭流以及其缓冲区。 ;) - Peter Lawrey
2
Base64减缓了这个问题。 :) - LunaVulpo
谢谢,我应该知道这是一个编码问题。在我的情况下,我需要使用org.apache.tomcat.util.codec.binary.Base64来创建字符串和Base64.encodeBase64String来解码它。 - Ralph Ritoch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接