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

Mik*_*Hay 48 java stream resource-leak java-8 java-stream

Stream的javadoc说明:

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

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

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

Bri*_*etz 61

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

这里的操作设计原则是"获取资源的人应该释放资源".阅读EOF时文件不会自动关闭; 我们希望打开文件的人明确关闭文件.由IO资源支持的流是相同的.

幸运的是,该语言为您提供了一种自动化的机制:try-with-resources.由于Stream实现AutoCloseable,您可以执行以下操作:

try (Stream<String> s = Files.lines(...)) {
    s.forEach(...);
}
Run Code Online (Sandbox Code Playgroud)

"自动关闭真的很方便,所以我可以把它写成一个单行"的说法很好,但主要是尾巴摇尾巴.如果您打开了文件或其他资源,则还应该准备关闭它.有效和一致的资源管理胜过"我想在一行中写出来",我们选择不扭曲设计只是为了保持一线性.

  • 在我看来,这很难注意到.它不在Files.lines()javadoc中,如果您将终止操作放在同一行并且没有Stream作为变量,则Eclipse不会警告资源未被关闭. (8认同)
  • 我想这里的基本原理是,如果存在未处理的异常,则 Stream 可能不会“一直读取”,然后底层句柄将“永远不会关闭”。所以这避免了这个问题。太糟糕了,它打破了流链接,并且令人困惑,因为“大多数其他流”不需要这种范式。那么你什么时候对 Stream 类型的对象使用 Try-with-Resources 呢?有时……但又不是其他时候。出现 #close 方法永远不会在正常管道中调用,即使管道已“完成”... (4认同)

Tag*_*eev 15

除了@BrianGoetz之外,我还有更具体的例子.不要忘记Stream有逃生舱的方法iterator().假设你这样做:

Iterator<String> iterator = Files.lines(path).iterator();
Run Code Online (Sandbox Code Playgroud)

之后,你可以调用hasNext()next()几次,然后就放弃这个迭代器Iterator接口完美支持这样使用.没有办法明确关闭Iterator,你可以在这里关闭的唯一对象是Stream.所以这样它可以很好地工作:

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.
Run Code Online (Sandbox Code Playgroud)


小智 5

此外,如果你想“一行写”。你可以这样做:

Files.readAllLines(source).stream().forEach(...);
Run Code Online (Sandbox Code Playgroud)

如果您确定需要整个文件并且文件很小,则可以使用它。因为它不是懒惰的阅读。

  • 请注意,此处不需要`.stream()`。 (5认同)
  • 并且您必须确保文件不会太大而无法放入内存。 (5认同)