如何附加到现有的 java.io.ObjectStream? [复制]

Posted

技术标签:

【中文标题】如何附加到现有的 java.io.ObjectStream? [复制]【英文标题】:How can I append to an existing java.io.ObjectStream? [duplicate] 【发布时间】:2011-01-06 20:50:57 【问题描述】:

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

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

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

【问题讨论】:

是否可以使用数据库进行持久化? @danben:是的,问题是是否可以将对象附加到文件中。 @Asaph:我可以这样做,但我想知道 append 是否适用于对象。抱歉,如果我的问题没有说明这一点。 那是ObjectOutputStream 吗? @Oscar:是的,ObjectInputStream 和 ObjectOutputStream ...你是如何附加对象的? 【参考方案1】:

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

【讨论】:

【参考方案2】:

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

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

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

【讨论】:

非常好, void write(byte[] b, int off, int len, boolean copy) 不幸地使用了私有方法 writeBlockHeader。 +1 我在文档中读到了这一点,但是(因为它经常发生在我的文档中)直到现在我才完全明白。 @stacker 我看不出这是如何导致任何问题的。该标头是序列化流格式的一部分,表示将接下来的 n 个字节作为单字节数组读取,并且在以后使用流时将被正确读取。 很好的小费,杰夫。再一次,SO 让我意识到了一种新技术【参考方案3】:

我在这个主题上找到的最好的文章是: 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 的长度和内容通过 数据输出流。

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

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

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

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

【讨论】:

这里有一对实现解决方案 #1 的函数(在 Scala 中)【参考方案4】:

非常感谢 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
        
    

【讨论】:

以上是关于如何附加到现有的 java.io.ObjectStream? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何使用附加模式将音频录制到现有的音频文件中?

如何附加到现有的 java.io.ObjectStream? [复制]

如何附加到现有的屏幕会话或创建新的屏幕会话并运行命令?

如何将行附加到现有的 SYS_REFCURSOR?

如何将 Excel 转换为 JSON 并将其附加到现有的 JSON 文件?

如何使用 php 类 DOMDocument 将新的 xml 节点附加到现有的 .xml 文件?