为什么 Files.lines(和类似的 Streams)没有自动关闭?

流的 javadoc 声明:

流具有 BaseStream.close() 方法并实现 AutoCloseable,但几乎所有流实例在使用后实际上并不需要关闭。通常,只有源是 IO 通道的流(例如 Files.lines(Path,Charset)返回的流)才需要关闭。大多数流由集合、数组或生成函数提供支持,这些函数不需要特殊的资源管理。(如果流确实需要关闭,则可以在 try-with-resources 语句中将其声明为资源。

因此,绝大多数情况下,可以在单行流中使用流,例如但对于其他资源支持的流,必须使用 try-with-resources 语句,否则会泄漏资源。collection.stream().forEach(System.out::println);Files.lines

这让我觉得容易出错和没有必要。由于 Streams 只能迭代一次,因此在我看来,不存在输出在迭代后立即不应关闭的情况,因此实现应该在任何终端操作结束时隐式调用 close。我错了吗?Files.lines


答案 1

是的,这是一个深思熟虑的决定。我们考虑了这两种选择。

这里的操作设计原则是“谁获得资源,谁就应该释放资源”。当您阅读EOF时,文件不会自动关闭;我们希望打开文件的人会明确关闭它们。由 IO 资源支持的流是相同的。

幸运的是,该语言为您提供了一种自动执行此操作的机制:尝试使用资源。由于 Stream 实现了 AutoCloseable,因此您可以执行以下操作:

try (Stream<String> s = Files.lines(...)) {
    s.forEach(...);
}

“自动关闭会非常方便,所以我可以把它写成一行”的论点很好,但主要是摇尾巴的狗。如果打开了文件或其他资源,则还应准备好将其关闭。有效和一致的资源管理胜过“我想在一行中写这个”,我们选择不扭曲设计只是为了保持一行性。


答案 2

除了@BrianGoetz答案之外,我还有更具体的例子。不要忘记,具有逃生舱口方法,例如.假设您正在执行以下操作:Streamiterator()

Iterator<String> iterator = Files.lines(path).iterator();

之后,您可以调用和几次,然后放弃此迭代器:接口完全支持此类使用。没有办法显式关闭 ,您在此处唯一可以关闭的对象是 .所以这样它就可以完美地工作:hasNext()next()IteratorIteratorStream

try(Stream<String> stream = Files.lines(path)) {
    Iterator<String> iterator = stream.iterator();
    // use iterator in any way you want and abandon it at any moment
} // file is correctly closed here.

推荐