Как двоичный (де) сериализовать объект в строку/form?

Мне нужно сериализовать объекты в String и десериализовать.

Я прочитал sugestion on 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

Используйте
 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); вместо ByteArrayInputStream bais = new ByteArrayInputStream(out.getBytes());, поскольку преобразование String искажает данные (из-за кодировки).

Если вам действительно нужно сохранить результат в String, вам нужен безопасный способ хранения произвольных байтов в String. Один из способов сделать это для нас Base64-кодирование.

Совершенно другой подход состоял бы в том, чтобы не использовать стандартную сериализацию Java для этого класса, а создавать собственный конвертер данных в/из String.

Ответ 2

Не совсем верно сказать, что преобразование в строку искажает данные. Преобразование в "UTF-8" происходит потому, что оно не является биективным (некоторые символы имеют 2 байта, но не все 2 байтовые последовательности разрешены в виде последовательностей символов), тогда как "ISO-8859-1" является биективным (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();
    }
}