在 Java 中关闭嵌套流和写入器的正确方法

2022-08-31 10:47:09

注意:这个问题及其大部分答案可以追溯到Java 7发布之前。Java 7 提供了自动资源管理功能,可以轻松地执行此操作。如果您使用的是Java 7或更高版本,则应转到Ross Johnson的答案


在 Java 中关闭嵌套流的最佳、最全面的方法是什么?例如,考虑以下设置:

FileOutputStream fos = new FileOutputStream(...)
BufferedOS bos = new BufferedOS(fos);
ObjectOutputStream oos = new ObjectOutputStream(bos);

我理解关闭操作需要投保(可能通过使用final条款)。我想知道的是,是否有必要明确确保嵌套流已关闭,或者是否只需确保关闭外部流(oos)就足够了?

我注意到的一件事,至少在处理这个特定的例子时,是内部流似乎只抛出了FileNotFoundExceptions。这似乎意味着,从技术上讲,没有必要担心如果它们失败就关闭它们。

这是一位同事写的:


从技术上讲,如果正确实现,关闭最外层的流(oos)应该就足够了。但实现似乎有缺陷。

示例:BufferedOutputStream 从 FilterOutputStream 继承 close(),后者将其定义为:

 155       public void close() throws IOException {
 156           try {
 157             flush();
 158           } catch (IOException ignored) {
 159           }
 160           out.close();
 161       }

但是,如果 flush() 由于某种原因引发运行时异常,则永远不会调用 out.close()。因此,主要担心关闭FOS似乎是“最安全”(但丑陋)的,FOS使文件保持打开状态。


什么被认为是关闭嵌套流的最佳方法,当您绝对需要确定时?

是否有任何官方的Java / Sun文档可以详细处理这个问题?


答案 1

关闭链接流时,只需关闭最外层的流。任何错误都将沿链向上传播并被捕获。

有关详细信息,请参阅 Java I/O 流

解决问题

但是,如果 flush() 由于某种原因引发运行时异常,则永远不会调用 out.close()。

这是不对的。在捕获并忽略该异常后,执行将在 catch 块之后拾取备份,并且将执行该语句。out.close()

您的同事对运行时异常提出了一个很好的观点。如果您绝对需要关闭流,则始终可以尝试从外到内单独关闭每个流,在第一个异常处停止。


答案 2

在Java 7时代,尝试使用资源肯定是要走的路。如前面的几个答案中所述,关闭请求从最外层的流传播到最内层的流。因此,只需要一次关闭即可。

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
  // do something with ois
}

但是,此模式存在问题。资源试用不会识别内部的 FileInputStream,因此,如果 ObjectInputStream 构造函数引发异常,则 FileInputStream 永远不会关闭(直到垃圾回收器到达它)。解决方案是...

try (FileInputStream fis = new FileInputStream(f); ObjectInputStream ois = new ObjectInputStream(fis)) {
  // do something with ois
}

这不那么优雅,但更坚固。这是否真的是一个问题将取决于在构造外部对象期间可以引发哪些异常。ObjectInputStream可以抛出IOException,它很可能由应用程序处理而不会终止。许多流类仅引发未经检查的异常,这很可能导致应用程序终止。


推荐