附加到ObjectOutputStream

时间:2009-07-28 14:51:03

标签: java serialization append objectoutputstream objectinputstream

是否无法附加到ObjectOutputStream

我正在尝试追加对象列表。以下片段是一个在作业完成时调用的函数。

FileOutputStream fos = new FileOutputStream
           (preferences.getAppDataLocation() + "history" , true);
ObjectOutputStream out = new ObjectOutputStream(fos);

out.writeObject( new Stuff(stuff) );
out.close();

但是当我尝试阅读它时,我只得到文件中的第一个。 然后我得到java.io.StreamCorruptedException

阅读我正在使用

FileInputStream fis = new FileInputStream
        ( preferences.getAppDataLocation() + "history");
ObjectInputStream in = new ObjectInputStream(fis);    

try{
    while(true)
        history.add((Stuff) in.readObject());
}catch( Exception e ) { 
    System.out.println( e.toString() );
}

我不知道会有多少个物品存在所以我正在读书,而没有例外。从谷歌所说的这是不可能的。我想知道是否有人知道某种方式?

6 个答案:

答案 0 :(得分:73)

这是诀窍:子类ObjectOutputStream并覆盖writeStreamHeader方法:

public class AppendingObjectOutputStream extends ObjectOutputStream {

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

  @Override
  protected void writeStreamHeader() throws IOException {
    // do not write a header, but reset:
    // this line added after another question
    // showed a problem with the original
    reset();
  }

}

要使用它,只需检查历史文件是否存在,并实例化此可附加流(如果文件存在=我们追加=我们不想要标题)或原始流(如果文件存在不存在=我们需要一个标题)。

修改

我对班级的第一个命名感到不满意。这个更好:它描述了“它是什么”而不是“如何完成”

修改

再次更改名称,以澄清此流仅用于附加到现有文件。它不能用于创建包含对象数据的 new 文件。

修改

this question显示刚刚覆盖reset()为无操作的原始版本在某些条件下创建无法读取的流后,添加了对writeStreamHeader的调用

答案 1 :(得分:13)

正如API所说,ObjectOutputStream构造函数将序列化流标头写入基础流。并且该头文件在文件的开头只有一次。所以打电话

new ObjectOutputStream(fos);

引用同一文件的FileOutputStream多次将多次写入标题并破坏文件。

答案 2 :(得分:7)

由于序列化文件的格式准确,追加确实会破坏它。您必须将所有对象作为同一个流的一部分写入文件,否则当它在期望对象时读取流元数据时会崩溃。

您可以阅读Serialization Specification了解更多详情,或者(更简单)阅读this thread,其中Roedy Green基本上说的就是我刚才所说的。

答案 3 :(得分:5)

避免此问题的最简单方法是在编写数据时保持OutputStream处于打开状态,而不是在每个对象之后关闭它。可能建议调用reset()以避免内存泄漏。

另一种方法是将文件读作一系列连续的ObjectInputStreams。但是这需要你继续计算你读取的字节数(这可以通过FilterInputStream实现),然后关闭InputStream,再次打开它,跳过那么多字节,然后将它包装在ObjectInputStream()中。

答案 4 :(得分:0)

每次附加对象之前,读取并复制文件中的所有当前数据,然后将所有数据一起覆盖到文件中。

答案 5 :(得分:0)

我已经扩展了公认的解决方案,以创建一个可用于附加和创建新文件的类。

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;

public class AppendableObjectOutputStream extends ObjectOutputStream {

    private boolean append;
    private boolean initialized;
    private DataOutputStream dout;

    protected AppendableObjectOutputStream(boolean append) throws IOException, SecurityException {
        super();
        this.append = append;
        this.initialized = true;
    }

    public AppendableObjectOutputStream(OutputStream out, boolean append) throws IOException {
        super(out);
        this.append = append;
        this.initialized = true;
        this.dout = new DataOutputStream(out);
        this.writeStreamHeader();
    }

    @Override
    protected void writeStreamHeader() throws IOException {
        if (!this.initialized || this.append) return;
        if (dout != null) {
            dout.writeShort(STREAM_MAGIC);
            dout.writeShort(STREAM_VERSION);
        }
    }

}

此类可用作ObjectOutputStream的直接扩展替代。 我们可以使用以下类:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class ObjectWriter {

    public static void main(String[] args) {

        File file = new File("file.dat");
        boolean append = file.exists(); // if file exists then append, otherwise create new

        try (
            FileOutputStream fout = new FileOutputStream(file, append);
            AppendableObjectOutputStream oout = new AppendableObjectOutputStream(fout, append);
        ) {
            oout.writeObject(...); // replace "..." with serializable object to be written
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}