2015-12-03 44 views
25

用于流的Javadoc指出:为什么Files.lines(和类似的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操作。我错了吗?

+1

根据我的经验,当你不想让它们自动关闭的流几乎不可能使用。你无法重新开启已经关闭的*。标记,重置,寻找。根据实施情况,您可以使用同一个流多次读取一些数据。 – ebyrob

+2

@ebyrob不与那个流 – assylias

+3

不比一个简单的尝试与资源,但如果你真的需要用一个单一的表达式:http://stackoverflow.com/a/31179709/2711488 – Holger

回答

29

是的,这是一个故意的决定。我们考虑了两种选择。

这里的操作设计原则是“获取资源的实体应释放资源”。当您读取EOF时文件不会自动关闭;我们预计文件将被明确关闭。由IO资源支持的流是相同的。

幸运的是,该语言提供了一种为您自动执行此操作的机制:尝试使用资源。由于流实现AutoCloseable,你可以这样做:

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

说:“这将是非常方便的自动关闭,所以我可以把它写成一个班轮”是好的,但将主要是尾巴摇参数狗。如果你打开一个文件或其他资源,你也应该准备关闭它。有效和一致的资源管理胜过“我想把它写成一行”,并且我们选择不扭曲设计来保持单线性。

+0

我想这里的基本原理是,如果有一个未处理的异常,流可能不是“一路读”,然后底层句柄将是“从未关闭。“所以这可以避免这个问题。太糟糕了,它打破了串流链,并且令人困惑,因为“大多数其他流”不需要这种模式。那么,你什么时候使用类型为Stream的对象的Try-with-Resources?有时......但是再次不是其他时间。出现#close方法永远不会在正常的管道中调用,即使管道已“完成”... – rogerdpack

+1

在我看来,这很难被注意到。它不在Files.lines()javadoc中,如果将终止操作放在同一行中,并且没有将Stream作为变量,Eclipse不会警告资源未关闭。 – aalku

12

我除了@BrianGoetz答案还有更具体的例子。不要忘记,Stream有逃生方法,如iterator()。假设你正在做的:

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

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

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

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

+2

请注意,'.stream()'在这里是不必要的。 –

+3

而且您必须确定该文件不太大以致无法放入内存。 – Oliv

0

如果你像我一样懒惰,不介意“如果发生异常,它会使文件句柄处于打开状态”,你可以将该流封装在自动关闭流中,像这样(可能有其他方式):

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

这不起作用。没有保证流消耗所有元素。有像'find ...()'或'... Match(...)',还有'limit(...)'和'takeWhile(...)'这样的短路操作。如果一个应用程序用'iterator()'或'spliterator()'终止流,那么也没有保证它会迭代到最后。因此,您的解决方案仅为少数用例提供服务,同时显着降低效率 – Holger

+0

还好点,谢谢! (如果你仔细阅读所有内容,那么这些作品就可以工作,但如果情况并非如此,那么最好不要使用它)。或者,也许有些人会认为它是一个功能,例如,您可以通过该流,从打开它的方法中取出,并且在它最终用完时仍然优雅地自我关闭:) – rogerdpack

相关问题