关闭Java 8流

59
如果我们像这样使用Java 8 Stream list.stream().filter(....).collect(..).....,那么这个流什么时候关闭?
我们是否应该像下面的例子那样手动关闭这个流是一种好的做法吗?
Stream<String> stream = list.stream();
String result = stream.limit(10).collect(Collectors.joining(""));
stream.close();

10
从文档中得知,流(Streams)有一个BaseStream.close()方法,并实现了AutoCloseable接口,但几乎所有的流实例在使用后都不需要被关闭。通常,只有那些源自IO通道的流(例如由Files.lines(Path, Charset)返回的流)需要被关闭。大多数流都由集合,数组或生成函数支持,这些都不需要特殊的资源管理。(如果一个流确实需要关闭,可以在try-with-resources语句中声明它作为资源。) - Jan B.
3
通常情况下,流不需要关闭。只有一些访问资源的流,例如DirectoryStream,需要关闭。最好的方法是使用“try-with-resources”语句。具体使用方法请见官方文档 - Jesper
1
  1. 因为它们是懒加载的,所以没有被初始化。
  2. 它们实现了 AutoCloseable 接口,因此当流处于空闲状态时,打开的流会被关闭。
- bananas
4
这怎么算是重复问题?这个问题询问流是否应该关闭,而另一个问题则询问它们在某些特定情况下是否会被自动关闭。如果您因为拥有金徽章的权限可以单方面关闭问题,那么至少您应该仔细阅读它们。 - Malcolm
1
我有一个类似的问题,阅读了一些回答后,调用close方法是很浪费的。首先,你必须在每个close方法中添加额外的try-catch块,这可能会耗费时间。其次,任何close方法可能只是终止资源,因此您可以通过使用mystream = null;语句获得相同的效果。第三,流不直接从文件本身读取,因此文件不会像其他一些语言中那样保持打开状态,直到完成为止,因此没有必要关闭它们。这就是我理解的st。 - user12638373
显示剩余2条评论
5个回答

88

一般来说,不需要关闭所有流。你只需要关闭使用IO资源的流。

根据流文件文档

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

如果需要关闭流,则最佳做法是使用try-with-resources语句:

try ( Stream<String> stream = Files.lines(path, charset) ) {
    // do something
}

9

默认情况下,您应该关闭流。

流是一个非常通用的API;它代表了一系列数据,而不需要数据的使用者理解数据来自何处。

关闭不需要关闭的流不会产生任何成本;而未能关闭需要关闭的流可能会导致严重问题。您有多么确定您正在编写的代码,目前消耗的数据流不需要关闭,将来永远不会被重新用于消耗需要关闭的不同类型的数据流?

我刚刚完成了大量代码的重构,以前使用内存数据库,现在改为使用SQL后端。涉及的代码经常使用流,这是有充分理由的。将JDBC结果集封装在流中意味着我可以轻松地实现我的目标。但是...我的新流封装了一个需要关闭的资源,而旧流则没有。因为原始开发人员(在这种情况下是我,我很自责)没有关闭流,所以需要进行许多繁琐的调试。


1
在大多数情况下,这种说法过于绝对了,因为你通常是知道数据流的来源的。只有那些将流作为参数传递的API才不会知道。即使在困扰你的情况下,你也知道数据来自哪里... - davidbak
2
不,通常情况下你不应该关闭一个流。只有当你从明确要求关闭的地方获取它时,才应该关闭它。"关闭一个不需要关闭的流是没有成本的",成本在于不必要的代码会增加混乱和使代码更难读懂。 - Lii

8
我必须补充一点,流默认情况下根本不会关闭!
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);

// "--" is not printed at all:
list.stream().onClose(()->System.out.println("--")).forEach(x -> System.out.println(x));

System.out.println("with try(){}:");

// "--" is printed:
try (Stream<Integer> stream = list.stream() ) {
    stream.onClose(() -> System.out.println("--")).forEach(x -> System.out.println(x));
}

1

正如我不断强调的那样,这取决于您的用例。 我使用的推论是:用例的上下文决定了代码。

因此,文件(如已经说明的那样),扫描器和套接字需要手动关闭。 规则是关闭所有基于IO的流。

那么集合,数组和生成器呢? 引用Baeldung文章中的话:我们应该关闭Java Stream吗?

大多数情况下,我们从Java集合、数组或生成器函数创建Stream实例。例如,在这里,我们通过Stream API操作String集合:

List colors = List.of("Red", "Blue", "Green") .stream()
.filter(c -> c.length() > 4) .map(String::toUpperCase)
.collect(Collectors.toList());

有时,我们会生成一个有限或无限的顺序流:

Random random = new Random(); random.ints().takeWhile(i -> i < 1000).forEach(System.out::println);

此外,我们还可以使用基于数组的流:

String[] colors = {"Red", "Blue", "Green"}; Arrays.stream(colors).map(String::toUpperCase).toArray()

在处理这些类型的流时,我们不应该显式地关闭它们。与这些流相关的唯一有价值的资源是内存,垃圾回收(GC)会自动处理它。

我之前不知道的另一个用例是:

  • 无论是否关闭包含它们的流,传递给flatMap的流都将被关闭。来自javadocs

返回一个流,其中包含将此流的每个元素替换为应用提供的映射函数到每个元素上产生的映射流的内容的结果。每个映射流在其内容被放入此流后都会被关闭。(如果映射流为空,则使用空流。)

感谢Mike在使用Autoclosable关闭Java流中指出了这一点。

因此,使用情况确定何时以及如何关闭流。


0

你需要关闭的流之一是来自org.hibernate.query.Query#getResultStream的流。

你可以使用try-with-resources来实现:

try (Stream<SomeEntity> resultStream = query.getResultStream()) {
    // process the stream
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接