【问题标题】:Why doesn't Java close() stream after a terminal operation is issued?为什么发出终端操作后 Java close() 不流式传输?
【发布时间】:2015-03-02 15:38:20
【问题描述】:

阅读https://www.airpair.com/java/posts/spring-streams-memory-efficiency 后,我很想将结果从数据库中流出,但正如我与一位同事讨论的那样(cfr. 他在该文章中添加的评论),需要记住使用 try-with-resources构造以避免任何内存泄漏。

  1. 为什么 Java 8 库不负责在每个 terminal operation 之后自行关闭流(无需将流实例包装在 try-with-resources 中)?
  2. 如果适用,是否有任何计划将此功能添加到 Java 中,或者提出请求是否有意义?

【问题讨论】:

  • terminal operation 是什么意思?
  • 我认为您将java.io.InputStream 与java.util.stream.Stream 混为一谈,这是两个非常不同的概念
  • @Zoltán 不,我说的是java.util.Stream,它是AutoCloseable,因此可以在try-with-resources 中使用。 @MartinSerrano 我添加了一个指向 Java 文档的链接,该链接涉及一般的流,特别是终端操作。
  • 我不明白你的第一个问题。这没有太大意义。
  • try-with-resources 语法是准确完成您所要求的事情的正确方法。

标签: java java-8 java-stream


【解决方案1】:

因为需要显式资源释放的流实际上是一种非常不寻常的情况。因此,我们选择不使用仅对 0.01% 的使用有价值的东西来负担所有流执行。

我们制作了 Stream Autocloseable,以便您可以根据需要从源中释放资源,但这就是我们停止的地方,并且有充分的理由。

这样做不仅会自动给大多数用户带来他们不需要的额外工作,而且还会违反一般原则:分配资源的人负责关闭资源。当你打电话时

BufferedReader reader = ...
reader.lines().op().op()...

你是打开资源的人,而不是流库,你应该关闭它。事实上,由于在某些资源持有对象上调用访问器方法而关闭流有时会关闭底层对象,因此您可能不希望流为您关闭 BufferedReader - 您可能希望它保持打开状态通话后。

如果你想关闭资源,这也很简单:

try (BufferedReader reader = ...) {
    reader.lines().op()...
}

您可能正在以一种特定的方式使用流,因此流应该做什么似乎“显而易见”——但那里的用例比您的要多。因此,我们没有迎合特定用例,而是从一般原则着手:如果您打开了流,并且想要关闭它,请自己关闭它,但如果您没有打开它,则不适合您关闭。

【讨论】:

  • 更正:引入流是为了提供一种更抽象的方式来表达对任意数据集的聚合操作;这是实际并行性的必要先决条件,但我想每个人都会同意,即使没有并行性,Stream 也非常有用。对close() 和Autocloseable 的支持表示扩展设计以提供对源代表用户管理资源(与由VM 管理的内存相反)的流的最小支持。我认为这真的是“2% 空”与“98% 满”,你现在可能只是在 2% 中游泳。
  • doing this automagically [would] burden the majority of users with extra work that they don't need 这个我不明白:用户负担的工作是什么?自动呼叫close?无论如何,对于大多数流来说,这都是无操作的,我不明白这种负担。
  • 另外,对于终端操作iterator 和spliterator,我们无法在此时关闭流,因为如果我们这样做了,生成的迭代器/拆分器将无法工作。可以肯定的是,我们本可以采取其他政策,这也可能不是“错误”的,但我们选择的政策是基于明确和明智的原则。
  • @BrianGoetz 我花了一整天的时间来找出像boolean res = Files.list(dir).anyMatch(e -> true); 这样的简单行留下未关闭的资源。请注意,实际上不是我打开了资源,它是在 Files 类中打开的,它的行为在那里被描述为 .onClose(()->ds.close());。以您的示例为例,bufferedReader.lines().op().op()...lines() 方法中没有添加流关闭处理程序,因此即使流最终关闭,用户仍然需要自己处理 bufferedReader。
  • @BrianGoetz terminalOperator 应该是真正的终端。如果流不再可用,让流打开有什么问题?
【解决方案2】:

我认为您将java.io.InputStream 与java.util.stream.Stream 混为一谈,这是两个非常不同的概念。

try-with-resources 作用于实现Autoclosable 接口的对象,例如InputStreams。 InputStreams 表示与 IO 相关的抽象数据源。

另一方面,java.util.stream.Stream<T> 实现了函数式编程的概念,它表示一种动态集合,不一定是静态构建的,而是可以生成的,因此可能是无限的。

Marko Topolnik(您链接到的文章的作者)在文章中的基本作用是建议一种将IO 源包装到java.util.stream.Stream 中的方法。这是一个非常聪明的方法,但java.util.stream.Streams 通常不用于此目的。

因为它们通常不打算与IO 一起使用,所以没有理由在终端操作之后包含关闭。


编辑:

在您澄清您实际上并没有混淆这两者之后(很抱歉假设如此),感谢this answer,我发现documentation of AutoCloseable 中回答了您的确切示例(重点由我自己添加):

基类实现是可能的,而且实际上很常见 AutoCloseable 即使不是所有的子类或实例都会 持有可释放的资源。对于必须完整运行的代码 一般性,或者当已知 AutoCloseable 实例 需要资源释放,推荐使用try-with-resources 建筑。 但是,当使用 Stream 等设施时, 支持基于 I/O 和非基于 I/O 的形式,try-with-resources 使用非基于 I/O 的表单时,通常不需要块。

【讨论】:

  • java.util.stream.Stream 也实现了Autoclosable,而这正是这个问题的意义所在。
【解决方案3】:

为什么 Java 8 库不负责在每次终端操作后自行关闭流(无需将流实例化包装在 try-with-resources 中)?

因为在终端操作期间或之前可能会发生异常,并且您可能不希望终端操作关闭流。如果您确实希望流关闭,可以使用 try-with-resource。

如果适用,是否有将此功能添加到 Java 的任何计划,或者请求它是否有意义?

这没有意义,请参阅上面的答案。

【讨论】:

  • 你有这方面的参考吗?
  • @MartinSerrano 到底是什么的参考?在执行终端操作之前打开流并引发异常是微不足道的。
  • 为您解答。这只是你的意见吗?似乎@Zoltan 的答案更符合目标。
  • @MartinSerrano 我怀疑 Zoltan 和您自己都没有真正理解所提出的问题。只要语言中的所有内容都可以进行某种解释,那么是的,这是一个见仁见智的问题;但如果我正确理解了这个问题,那么不,我在上面的答案中的陈述是事实。
  • 获取ArrayList,在Stream.foreach() 中抛出RuntimeException,然后尝试在Stream 上调用终端操作。即使你不抛出任何异常,Stream 在操作后也不能被重用。我看不出有任何理由在终端操作后不自动关闭Stream。
猜你喜欢
  • 1970-01-01
  • 2021-07-02
  • 2014-12-19
  • 1970-01-01
  • 1970-01-01
  • 2020-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多