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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34072035/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 22:27:18  来源:igfitidea点击:

Why is Files.lines (and similar Streams) not automatically closed?

javastreamjava-8java-streamresource-leak

提问by MikeFHay

The javadoc for Stream states:

Stream 的 javadoc 指出:

Streams have a BaseStream.close() method and implement AutoCloseable, but nearly all stream instances do not actually need to be closed after use. Generally, only streams whose source is an IO channel (such as those returned by Files.lines(Path, Charset)) will require closing. Most streams are backed by collections, arrays, or generating functions, which require no special resource management. (If a stream does require closing, it can be declared as a resource in a try-with-resources statement.)

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

Therefore, the vast majority of the time one can use Streams in a one-liner, like collection.stream().forEach(System.out::println);but for Files.linesand other resource-backed streams, one must use a try-with-resources statement or else leak resources.

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

This strikes me as error-prone and unnecessary. As Streams can only be iterated once, it seems to me that there is no a situation where the output of Files.linesshould not be closed as soon as it has been iterated, and therefore the implementation should simply call close implicitly at the end of any terminal operation. Am I mistaken?

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

回答by Brian Goetz

Yes, this was a deliberate decision. We considered both alternatives.

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

The operating design principle here is "whoever acquires the resource should release the resource". Files don't auto-close when you read to EOF; we expect files to be closed explicitly by whoever opened them. Streams that are backed by IO resources are the same.

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

Fortunately, the language provides a mechanism for automating this for you: try-with-resources. Because Stream implements AutoCloseable, you can do:

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

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

The argument that "it would be really convenient to auto-close so I could write it as a one-liner" is nice, but would mostly be the tail wagging the dog. If you opened a file or other resource, you should also be prepared to close it. Effective and consistent resource management trumps "I want to write this in one line", and we chose not to distort the design just to preserve the one-line-ness.

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

回答by Tagir Valeev

I have more specific example in addition to @BrianGoetz answer. Don't forget that the Streamhas escape-hatch methods like iterator(). Suppose you are doing this:

除了@BrianGoetz 的回答之外,我还有更具体的例子。不要忘记,Stream有像 .escape-hatch 方法iterator()。假设你正在这样做:

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

After that you may call hasNext()and next()several times, then just abandon this iterator: Iteratorinterface perfectly supports such use. There's no way to explicitly close the Iterator, the only object you can close here is the Stream. So this way it would work perfectly fine:

之后你可能会调用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.

回答by Michael Starodynov

In addition if you want "one line write". You can just do this:

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

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

You can use it if you are sure that you need entire file and the file is small. Because it isn't a lazy read.

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

回答by rogerdpack

If you're lazy like me and don't mind the "if an exception is raised, it will leave the file handle open" you could wrap the stream in an autoclosing stream, something like this (there may be other ways):

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

  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);
  }