Java 关闭管道中间的小溪

Java 关闭管道中间的小溪,java,java-8,java-stream,Java,Java 8,Java Stream,当我执行这段代码时,它会在流管道中打开许多文件: publicstaticvoidmain(字符串[]args)引发IOException{ Files.find(path.get(“JAVA\u DOCS\u DIR/DOCS/api/”), 100,(path,attr)->path.toString().endsWith(“.html”)) .map(文件->runtimizeException(()->Files.line(文件,StandardCharsets.ISO_8859_1))

当我执行这段代码时,它会在流管道中打开许多文件:

publicstaticvoidmain(字符串[]args)引发IOException{
Files.find(path.get(“JAVA\u DOCS\u DIR/DOCS/api/”),
100,(path,attr)->path.toString().endsWith(“.html”))
.map(文件->runtimizeException(()->Files.line(文件,StandardCharsets.ISO_8859_1)))
.map(流::计数)
.forEachOrdered(System.out::println);
}
我得到一个例外:

java.nio.file.FileSystemException:/long/file/name:打开的文件太多
问题在于,
Stream.count
在遍历流时不会关闭流。但我不明白为什么不应该,因为这是一个终端操作。其他终端操作(如
reduce
forEach
)也是如此<另一方面,code>flatMap关闭它所包含的流

文档告诉我在必要时使用try with resources语句来关闭流。在我的例子中,我可以用如下内容替换
count
行:

Files.find(Paths.get("Java_8_API_docs/docs/api"), 100,
        (path, attr) -> path.toString().endsWith(".html"))
    .map(file -> applyAndClose(
        () -> Files.lines(file, StandardCharsets.ISO_8859_1),
        Stream::count))
    .forEachOrdered(System.out::println);
/**
 * Applies a function to a resource and closes it afterwards.
 * @param sup Supplier of the resource that should be closed
 * @param op operation that should be performed on the resource before it is closed
 * @return The result of calling op.apply on the resource 
 */
private static <A extends AutoCloseable, B> B applyAndClose(Callable<A> sup, Function<A, B> op) {
    try (A res = sup.call()) {
        return op.apply(res);
    } catch (RuntimeException exc) {
        throw exc;
    } catch (Exception exc) {
        throw new RuntimeException("Wrapped in applyAndClose", exc);
    }
}
Files.find(Paths.get("JAVA_DOCS_DIR/docs/api/"),
           100, (path, attr) -> path.toString().endsWith(".html"))
     .mapToLong(this::lineCount)
     .forEachOrdered(System.out::println);
.map(s->{long c=s.count();s.close();返回c;})
但这既嘈杂又丑陋,在某些情况下,对于大型复杂的管道来说,这可能是一个真正的不便

因此,我的问题如下:

  • 为什么流的设计没有使终端操作关闭它们正在处理的流?这将使它们更好地处理IO流
  • 关闭管道中IO流的最佳解决方案是什么


  • RuntimeException
    是一种将选中的异常包装在
    RuntimeException
    s中的方法。

    您需要在此流操作中调用
    close()
    ,这将导致调用所有底层关闭处理程序

    更好的方法是将整个语句包装在try with resources块中,这样它将自动调用close处理程序

    这在您的情况下可能不太可能,这意味着您需要在某些操作中自行处理。您当前的方法可能根本不适合流

    似乎您确实需要在第二次
    map()
    操作中执行此操作。

    仅应调用一次关闭接口AutoCloseable。有关详细信息,请参阅的文档

    如果最终操作将自动关闭流,则可能会调用close两次。请看以下示例:

    try (Stream<String> lines = Files.lines(path)) {
        lines.count();
    }
    
    try(流行=文件。行(路径)){
    line.count();
    }
    

    正如现在定义的那样,第行上的close方法将只调用一次。无论最终操作是否正常完成,或操作是否因IOException异常而中止。如果流在最终操作中被隐式关闭,则如果发生IOException,将调用close方法一次,如果操作成功完成,将调用close方法两次。

    这里有一个替代方法,它使用
    文件中的另一个方法,并将避免泄漏文件描述符:

    Files.find(Paths.get("JAVA_DOCS_DIR/docs/api/"),
        100, (path, attr) -> path.toString().endsWith(".html"))
        .map(file -> runtimizeException(() -> Files.readAllLines(file, StandardCharsets.ISO_8859_1).size())
        .forEachOrdered(System.out::println);
    

    与您的版本不同,它将返回
    int
    而不是
    long
    作为行计数;但是,你没有那么多行的文件,是吗?

    可以创建一个实用的方法,它可以可靠地关闭管道中间的流。 这样可以确保每个资源都使用try with resource语句关闭,但不需要自定义实用程序方法,并且比直接在lambda中编写try语句要简单得多

    使用此方法,问题的管道如下所示:

    Files.find(Paths.get("Java_8_API_docs/docs/api"), 100,
            (path, attr) -> path.toString().endsWith(".html"))
        .map(file -> applyAndClose(
            () -> Files.lines(file, StandardCharsets.ISO_8859_1),
            Stream::count))
        .forEachOrdered(System.out::println);
    
    /**
     * Applies a function to a resource and closes it afterwards.
     * @param sup Supplier of the resource that should be closed
     * @param op operation that should be performed on the resource before it is closed
     * @return The result of calling op.apply on the resource 
     */
    private static <A extends AutoCloseable, B> B applyAndClose(Callable<A> sup, Function<A, B> op) {
        try (A res = sup.call()) {
            return op.apply(res);
        } catch (RuntimeException exc) {
            throw exc;
        } catch (Exception exc) {
            throw new RuntimeException("Wrapped in applyAndClose", exc);
        }
    }
    
    Files.find(Paths.get("JAVA_DOCS_DIR/docs/api/"),
               100, (path, attr) -> path.toString().endsWith(".html"))
         .mapToLong(this::lineCount)
         .forEachOrdered(System.out::println);
    
    实现如下所示:

    Files.find(Paths.get("Java_8_API_docs/docs/api"), 100,
            (path, attr) -> path.toString().endsWith(".html"))
        .map(file -> applyAndClose(
            () -> Files.lines(file, StandardCharsets.ISO_8859_1),
            Stream::count))
        .forEachOrdered(System.out::println);
    
    /**
     * Applies a function to a resource and closes it afterwards.
     * @param sup Supplier of the resource that should be closed
     * @param op operation that should be performed on the resource before it is closed
     * @return The result of calling op.apply on the resource 
     */
    private static <A extends AutoCloseable, B> B applyAndClose(Callable<A> sup, Function<A, B> op) {
        try (A res = sup.call()) {
            return op.apply(res);
        } catch (RuntimeException exc) {
            throw exc;
        } catch (Exception exc) {
            throw new RuntimeException("Wrapped in applyAndClose", exc);
        }
    }
    
    Files.find(Paths.get("JAVA_DOCS_DIR/docs/api/"),
               100, (path, attr) -> path.toString().endsWith(".html"))
         .mapToLong(this::lineCount)
         .forEachOrdered(System.out::println);
    
    /**
    *将函数应用于资源,然后将其关闭。
    *@param sup应关闭的资源的供应商
    *@param op应在资源关闭前对其执行的操作
    *@返回对资源调用op.apply的结果
    */
    专用静态B applyAndClose(可调用支持,函数op){
    try(res=sup.call()){
    返回op.apply(res);
    }捕获(RuntimeException exc){
    抛出exc;
    }捕获(异常exc){
    抛出新的RuntimeException(“包装在applyAndClose中”,exc);
    }
    }
    

    (由于需要关闭的资源在分配时通常也会抛出异常,因此非运行时异常被包装在运行时异常中,从而避免了使用单独的方法来完成此操作。)

    这里有两个问题:处理检查过的异常,如
    IOException
    ,以及及时关闭资源


    所有预定义的功能接口都没有声明任何已检查的异常,这意味着它们必须在lambda中处理,或者包装在未检查的异常中并重新启动。看起来您的
    runtimizeException
    函数就是这样做的。您可能还必须为它声明自己的函数接口。正如你可能已经发现的,这是一种痛苦

    关于关闭文件之类的资源,有一些调查表明,当到达流的末尾时,流将自动关闭。这会很方便,但不会处理抛出异常时的关闭。在streams中没有神奇的“做正确的事情”机制

    我们只剩下处理资源闭包的标准Java技术,即Java 7中引入的try with resources构造。TWR确实希望在调用堆栈中关闭与打开相同级别的资源。适用“谁打开它,谁就必须关闭它”的原则。TWR还处理异常处理,这通常使在同一位置处理异常处理和资源关闭变得很方便

    在本例中,流有点不寻常,因为它将
    映射到
    。这些嵌套流是未关闭的流,导致ev