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

时间:2015-12-03 17:07:11

标签: java stream java-8 java-stream resource-leak

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。任何终端操作结束。我错了吗?

4 个答案:

答案 0 :(得分:59)

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

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

幸运的是,该语言提供了一种为您自动执行此操作的机制:try-with-resources。因为Stream实现了AutoCloseable,所以你可以这样做:

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

论证&#34;自动关闭真的很方便,所以我可以把它写成一个单行&#34;很好,但主要是摇尾巴的尾巴。如果您打开了文件或其他资源,则还应该准备关闭它。有效和一致的资源管理胜过&#34;我想在一行中编写这个&#34;,我们选择不扭曲设计只是为了保留单行。

答案 1 :(得分:15)

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

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

之后你可以多次调用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.

答案 2 :(得分:4)

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

Files.readAllLines(source).stream().forEach(...);

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

答案 3 :(得分:1)

如果你像我一样懒,并且不介意“如果引发异常,它会使文件句柄保持打开状态”你可以将流包装在一个自动关闭的流中,就像这样(可能还有其他方法) ):

  static Stream<String> allLinesCloseAtEnd(String filename) throws IOException {
    Stream<String> lines = Files.lines(Paths.get(filename));
    Iterator<String> linesIter = lines.iterator();

    Iterator it = new Iterator() {
      @Override
      public boolean hasNext() {
        if (!linesIter.hasNext()) {
          lines.close(); // auto-close when reach end
          return false;
        }
        return true;
      }

      @Override
      public Object next() {
        return linesIter.next();
      }
    };
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, Spliterator.DISTINCT), false);
  }