【问题标题】:What is the best way to fully read a stream of objects from a file in Java?从 Java 文件中完全读取对象流的最佳方法是什么?
【发布时间】:2011-02-18 20:10:41
【问题描述】:

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

我注意到以下内容不起作用:

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

【问题讨论】:

    标签: java serialization


    【解决方案1】:

    您的解决方案似乎很好。只需确保您有一个 finally 子句,您可以在其中关闭您的流。

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

    【讨论】:

    • 编辑了问题以包含 EOFObject 解决方案。我写的是最好的方法吗?谢谢。
    • @jonderry 是的,看起来不错。请记住在 finally 块中关闭流(您无法避免该部分 - 所以只需 try / finally
    • 酷,EOFObject 解决方案和捕获 EOFException 之间是否有任何权衡?
    • @jonderry 除了可读性 - 我不这么认为。通常,使用异常来定义程序流不是一个好习惯,因此您要避免这种情况。在性能方面,我想差异可以忽略不计,如果有的话。你可以用大数据来衡量它们。
    • 我不明白。该方法在流结束时抛出 EOFException。所以使用它。
    【解决方案2】:

    我正在创建一个可能很长的对象日志,并且不想在写入文件之前将它们全部保存在内存中,因此我无法将对象的序列化集合写入文件

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

    反序列化文件时存在类似问题。为了解析反向引用,流很可能会保持所有先前读取的对象处于活动状态,以从序列化流中解析对这些对象的潜在反向引用。

    如果您确实需要能够在流完全写入之前释放这些对象,您可能希望为每个(批次)对象使用一个新的 ObjectOutputStream ObjectOutputStream.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();
    }
    

    请注意,关于被序列化的类的元数据将在每次重置后重新写入,这非常浪费(上面的程序每个 Integer 写入大约 80 个字节......),所以你不应该太频繁地重置,也许每一次100 个对象?

    对于检测流的结束,我发现 bozho 建议使用 EOF 对象最好。

    【讨论】:

    • 那么即使没有对已写入对象或其字段的剩余引用,它们也不会被垃圾回收,直到流关闭?
    • 是的,因为 ObjectOutputStream 无法知道您以后要序列化的其他对象,因此 ObjectInputStream 也不知道。
    • 如果之前写入的对象和以后要写入的对象之间没有依赖关系,为什么后面序列化的对象会很重要?
    • @jonderry 另见ObjectOutputStream.html#reset()
    • @jonderry:OOS 之前不知道是否存在依赖关系,因此它必须存储对曾经存储的所有对象(以及 ObjectInputStream)的引用,以便能够恢复对象到另一侧的同构图。如果你知道你不需要这个(并且不想要这个),reset 是正确的做法,可以避免阻塞垃圾收集。创建新的 ObjectOutpuStreams 没有用,因为您还必须在另一端创建新的 ObjectInputStreams。
    【解决方案3】:

    在每个对象后面写一个boolean,“最后一个”对象后面跟着一个false。因此,在您写出的流中:

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

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

    boolean 将非常紧凑地存储在序列化流中,因此不会增加文件大小。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2012-03-31
        • 2011-04-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-02
        • 2010-11-25
        • 2010-09-06
        相关资源
        最近更新 更多