有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

序列化在Java中,从文件中完全读取对象流的最佳方法是什么?

我正在创建一个可能很长的对象日志,不想在写入文件之前将它们全部保存在内存中,因此无法将对象的序列化集合写入文件。我试图在完成日志记录后,找出读取整个对象流的“最佳”方式

我注意到以下情况不起作用:

FileInputStream fis = new FileInputStream(log);
ObjectInputStream in = new ObjectInputStream(fis);
while ((obj = in.readObject()) != null) {
  // do stuff with obj
}

因为流在到达文件末尾时抛出异常,而不是返回null(可能是因为可以向对象流写入/读取null,从而导致上述循环的行为不符合预期)

有没有比以下更好的方法来完成我想通过上述循环完成的事情:

FileInputStream fis = new FileInputStream(log);
ObjectInputStream in = new ObjectInputStream(fis);
try {
  while (true) {
    obj = in.readObject();
    // do stuff with obj
  }
} catch (EOFException e) {
}

这似乎有点笨拙。对于文件结束对象解决方案,以下是最佳方法吗

private static final class EOFObject implements Serializable {
  private static final long serialVersionUID = 1L;
}

void foo() {
  Object obj;
  while (!((obj = in.readObject()) instanceof EOFObject)) {
    BidRequest bidRequest = ((BidRequestWrapper) obj).getBidRequest();
    bidRequestList.add(bidRequest);
  }
}

共 (4) 个答案

  1. # 1 楼答案

    你的代码不正确。readObject()在EOS时不返回null,它抛出EOFEException。所以抓住它。如果写入了Null,则返回Null。您不需要上面建议的所有布尔或标记对象

  2. # 2 楼答案

    I'm creating a potentially long log of objects and do not want to keep them all in memory before writing to a file, so I can't write a serialized collection of the objects to a file

    使用Java序列化时不满足此要求,因为序列化流维护对以前编写的对象的强引用,大概是为了在这些对象需要再次序列化时写回引用。这可以通过运行以下命令进行验证:

    public static void main(String[] args) throws Exception {
        OutputStream os = new FileOutputStream("C:\\test");
        ObjectOutputStream oos = new ObjectOutputStream(os);
        for (Integer i = 0; i < 1E9; i++) {
            oos.writeObject(i);
        }
        oos.close();
    }
    

    反序列化文件时也存在类似的问题。要解析回引用,流很可能会使所有以前读取的对象保持活动状态,以解析序列化流中对这些对象的潜在回引用

    如果您确实需要能够在流完全写入之前释放这些对象,那么您可能希望为每个(一批)对象使用一个新的ObjectOutputStreamObjectOutputStream.reset()-当然会失去解析早期流的反向引用的能力。也就是说,以下程序不会抛出OutOfMemoryError:

    public static void main(String[] args) throws Exception {
        OutputStream os = new FileOutputStream("C:\\test");
        ObjectOutputStream oos = new ObjectOutputStream(os);
        for (Integer i = 0; i < 1E9; i++) {
            oos.writeObject(i);
            oos.reset();
        }
        oos.close();
    }
    

    请注意,每次重置后,有关被序列化的类的元数据将被重新写入,这是非常浪费的(上面的程序每整数写入大约80字节…),所以你不应该经常重置,也许每100个对象重置一次

    对于检测流的结尾,我发现波佐关于EOF对象的建议是最好的

  3. # 3 楼答案

    在每个对象后面写一个boolean,最后一个对象后面跟一个false。所以,在你写出来的流中:

    true
    <object>
    true
    <object>
    true
    <object>
    false
    

    然后,当读回它们时,检查标志(您知道每个对象后面总是有一个标志)以决定是否读入另一个标志

    boolean将以非常紧凑的方式存储在序列化流中,因此不会增加太多文件大小

  4. # 4 楼答案

    你的解决方案似乎很好。只要确保有一个finally子句,在那里关闭流

    或者,您可以创建自己的EOF对象,并将其添加到末尾。因此,您可以检查当前读取的对象是否为EofObject,以及此时的break