如何附加到现有的java.io.ObjectStream?

时间:2010-01-19 15:37:56

标签: java serialization append objectinputstream

至于现在,当我尝试追加一个Object时,我会得到java.io.StreamCorruptedException。我已经在互联网上搜索了一种克服这种情况的方法。到目前为止我找到的答案是无法完成的。解决此问题的方法是将对象写入列表,然后将列表写入文件。

但是每次添加新对象时我都要覆盖该文件。这似乎不是加时赛的最佳解决方案。

有没有办法将对象附加到现有对象流?

4 个答案:

答案 0 :(得分:5)

实际上很容易做到。当您要添加到现有流时,需要使用ObjectOutStream的子类来覆盖writeStreamHeader,以便不在文件中间写入第二个标头。例如

class NoHeaderObjectOutputStream extends ObjectOutputStream {
  public NoHeaderObjectOutputStream(OutputStream os) {
    super(os);
  }
  protected void writeStreamHeader() {}
}

然后只需使用标准的ObjectInputStream来读取整个文件。

答案 1 :(得分:5)

我在这个主题上找到的最好的文章是: http://codify.flansite.com/2009/11/java-serialization-appending-objects-to-an-existing-file/

覆盖ObjectOutputStream的“解决方案”完全错误。我刚刚调查了一个由此引起的错误(浪费了两个宝贵的日子)。它不仅有时会损坏序列化文件,而且甚至设法读取而不抛出异常并最终提供垃圾数据(混合字段)。对于那些难以置信的人,我附上了一些暴露问题的代码:

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class Main {

    public static void main(String[] args) throws Exception {

        File storageFile = new File("test");
        storageFile.delete();

        write(storageFile, getO1());
        write(storageFile, getO2());
        write(storageFile, getO2());

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(storageFile));
        read(ois, getO1());
        read(ois, getO2());
        read(ois, getO2());
    }

    private static void write(File storageFile, Map<String, String> o) throws IOException {
        ObjectOutputStream oos = getOOS(storageFile);
        oos.writeObject(o);
        oos.close();
    }

    private static void read(ObjectInputStream ois, Map<String, String> expected) throws ClassNotFoundException, IOException {
        Object actual = ois.readObject();
        assertEquals(expected, actual);
    }

    private static void assertEquals(Object o1, Object o2) {
        if (!o1.equals(o2)) {
            throw new AssertionError("\n expected: " + o1 + "\n actual:   " + o2);
        }
    }

    private static Map<String, String> getO1() {
        Map<String, String> nvps = new HashMap<String, String>();
        nvps.put("timestamp", "1326382770000");
        nvps.put("length", "246");
        return nvps;
    }

    private static Map<String, String> getO2() {
        Map<String, String> nvps = new HashMap<String, String>();
        nvps.put("timestamp", "0");
        nvps.put("length", "0");
        return nvps;
    }

    private static ObjectOutputStream getOOS(File storageFile) throws IOException {
        if (storageFile.exists()) {
            // this is a workaround so that we can append objects to an existing file
            return new AppendableObjectOutputStream(new FileOutputStream(storageFile, true));
        } else {
            return new ObjectOutputStream(new FileOutputStream(storageFile));
        }
    }

    private static class AppendableObjectOutputStream extends ObjectOutputStream {

        public AppendableObjectOutputStream(OutputStream out) throws IOException {
            super(out);
        }

        @Override
        protected void writeStreamHeader() throws IOException {
            // do not write a header
        }
    }
}

如该文章所述,您可以使用以下解决方案之一:

  

解决方案#1:假冒单个流中的多个文件

     

...

     

将您的“交易”写入ByteArrayOutputStream,然后编写   此ByteArrayOutputStream的长度和内容通过   DataOutputStream类。

     

解决方案#2:重新打开并跳过

     

另一种解决方案是使用以下方法保存文件位置:

long pos = fis.getChannel().position();
     

关闭文件,重新打开文件,然后跳到此位置   在阅读下一笔交易之前。

答案 2 :(得分:2)

非常感谢 George Hategan 解决暴露代码的问题。我也检查了一会儿。然后,它击中了我。如果您使用子类 ObjectOutputStream 并重写 writeStreamHeader() 方法来写入数据,您必须使用并行子类 ObjectInputStream 并覆盖 readStreamHeader() 方法来阅读数据。当然,我们可以在写入和读取对象的不同实现之间进行锯齿形,但只要我们在写入/读取过程中使用相应的子类对 - 我们(希望)就可以了。汤姆。

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

public class SerializationDemo {

    public static void main(String[] args) throws Exception {
        File storageFile = new File("test.ser");
        storageFile.delete();
        write(storageFile, getO1());
        write(storageFile, getO2());
        write(storageFile, getO2());
        FileInputStream fis = new FileInputStream(storageFile);
        read(fis, getO1());
        read(fis, getO2());
        read(fis, getO2());
        fis.close();
    }

    private static void write(File storageFile, Map<String, String> o)
                    throws IOException {
        ObjectOutputStream oos = getOOS(storageFile);
        oos.writeObject(o);
        oos.flush();
        oos.close();
    }

    private static void read(FileInputStream fis, Map<String, String> expected)
                    throws ClassNotFoundException, IOException {
        Object actual = getOIS(fis).readObject();
        assertEquals(expected, actual);
        System.out.println("read serialized " + actual);
    }

    private static void assertEquals(Object o1, Object o2) {
        if (!o1.equals(o2)) {
            throw new AssertionError("\n expected: " + o1 + "\n actual:   " + o2);
        }
    }

    private static Map<String, String> getO1() {
        Map<String, String> nvps = new HashMap<String, String>();
        nvps.put("timestamp", "1326382770000");
        nvps.put("length", "246");
        return nvps;
    }

    private static Map<String, String> getO2() {
        Map<String, String> nvps = new HashMap<String, String>();
        nvps.put("timestamp", "0");
        nvps.put("length", "0");
        return nvps;
    }

    private static ObjectOutputStream getOOS(File storageFile)
                    throws IOException {
        if (storageFile.exists()) {
            // this is a workaround so that we can append objects to an existing file
            return new AppendableObjectOutputStream(new FileOutputStream(storageFile, true));
        } else {
            return new ObjectOutputStream(new FileOutputStream(storageFile));
        }
    }

    private static ObjectInputStream getOIS(FileInputStream fis)
                    throws IOException {
        long pos = fis.getChannel().position();
        return pos == 0 ? new ObjectInputStream(fis) : 
            new AppendableObjectInputStream(fis);
    }

    private static class AppendableObjectOutputStream extends
                    ObjectOutputStream {

        public AppendableObjectOutputStream(OutputStream out)
                        throws IOException {
            super(out);
        }

        @Override
        protected void writeStreamHeader() throws IOException {
            // do not write a header
        }
    }

    private static class AppendableObjectInputStream extends ObjectInputStream {

        public AppendableObjectInputStream(InputStream in) throws IOException {
            super(in);
        }

        @Override
        protected void readStreamHeader() throws IOException {
            // do not read a header
        }
    }
}

答案 3 :(得分:0)

您需要创建一个新的ObjectInputStream来匹配每个ObjectOutputStream。我不知道将状态从完整的ObjectInputStream转移到ObjectOutputStream的方法(没有完整的重新实现,这在纯Java中有点棘手)。