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

时间:2011-02-18 20:10:41

标签: java serialization

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

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

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 个答案:

答案 0 :(得分:5)

你的解决方案似乎很好。只需确保您有一个finally子句,然后关闭您的信息流。

或者,您可以创建自己的EOF对象,并在最后添加它。因此,您可以检查当前读取的对象是EofObject还是break

答案 1 :(得分:3)

  

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

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

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

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

答案 2 :(得分:2)

在每个对象后面写一个boolean,其中“last”对象后跟一个false。所以,在你写的你的流中:

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

然后,当他们重读时,你检查标志(你知道每个对象后面总会有一个)来决定是否要读另一个。

boolean将非常紧凑地存储在序列化流中,因此它不应该对文件大小增加太多。

答案 3 :(得分:0)

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