61

不能附加到一个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() );
}

我不知道会有多少对象出现,所以我正在阅读,没有例外。根据谷歌的说法,这是不可能的。我想知道是否有人知道方法?

4

6 回答 6

81

这是诀窍:子类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();
  }

}

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

编辑

我对班级的第一次命名不满意。这个更好:它描述的是“它的用途”而不是“它是如何完成的”

编辑

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

编辑

在这个问题reset()之后添加了一个调用,表明在某些情况下,刚刚被覆盖为无操作的原始版本可能会创建一个无法读取的流。writeStreamHeader

于 2009-07-28T15:58:06.743 回答
16

正如API所说,ObjectOutputStream构造函数将序列化流标头写入底层流。并且这个标题预计只有一次,在文件的开头。所以打电话

new ObjectOutputStream(fos);

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

于 2009-07-28T15:18:36.230 回答
8

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

您可以阅读序列化规范以获取更多详细信息,或者(更容易)阅读这个线程,其中 Roedy Green 基本上说了我刚才所说的内容。

于 2009-07-28T15:13:46.393 回答
7

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

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

于 2009-07-28T15:30:49.890 回答
3

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

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();
        }

    }

}
于 2019-08-07T15:07:13.177 回答
0

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

于 2014-11-09T22:12:01.187 回答