【问题标题】:Appending to an ObjectOutputStream附加到 ObjectOutputStream
【发布时间】:2010-11-14 17:52:33
【问题描述】:

不能追加到ObjectOutputStream吗?

我正在尝试追加到对象列表。在 sn-p 之后是一个在作业完成时调用的函数。

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

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

【问题讨论】:

  • 你是否从流中得到任何东西,或者它是否在循环中第一次抛出异常?
  • 它读取我保存的第一个对象然后我得到异常。
  • 在上面的代码中,我只看到一个对象写入文件,所以只会读取一个对象,对吧?
  • 第一个 sn-p 在一个函数中,每当作业完成时,它都会被多次调用。但我只能阅读我写的第一个。
  • 您的代码仅将 1 个对象写入流,是否有任何未显示的对象将超过 1 个对象写入该流?

标签: java serialization append objectoutputstream objectinputstream


【解决方案1】:

这是诀窍:子类 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 显示刚刚覆盖writeStreamHeader 成为空操作的原始版本在某些情况下可能会创建无法读取的流之后,添加了对reset() 的调用。

【讨论】:

  • 聪明!我认为流标头是唯一的问题,在这种情况下它应该可以很好地工作,但是您是否对其进行了测试以确定?
  • 感谢 cmets :-) 是的,我已经发布了经过测试的代码(只是忘了在答案中提及)
  • +1,谢谢。 警告: writeStreamHeader() 由 ObjectOutputStream constructor 调用。这使得单个类至少难以处理这两种情况。您可以创建一个返回 fileExists 的简单工厂方法吗? new AppendingObjectOutputStream(out) : new ObjectOutputStream(out);
  • 请大家注意,这仅用于附加到现有文件! new 文件必须使用普通的 ObjectOutputStream 创建,因为我们需要文件开头的标题。
  • 显然是这个解决方案有问题,如果你可以摆弄它。看这个问题:stackoverflow.com/questions/12279245/…
【解决方案2】:

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

new ObjectOutputStream(fos);

多次在FileOutputStream上引用同一个文件将多次写入标头并损坏文件。

【讨论】:

    【解决方案3】:

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

    您可以阅读Serialization Specification 了解更多详细信息,或者(更容易)阅读this thread,其中 Roedy Green 基本上说了我刚才所说的内容。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

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

          【讨论】:

            猜你喜欢
            • 2021-02-26
            • 2018-04-04
            相关资源
            最近更新 更多