【问题标题】:Java8 Stream of files, how to control the closing of files?Java8文件流,如何控制文件的关闭?
【发布时间】:2017-09-16 04:13:26
【问题描述】:

假设我有一个 Java8 Stream<FileReader> 并且我使用该流到 map 等,我如何控制流中使用的 FileReaders 的关闭?

请注意,我可能无法访问个人FileReaders,例如:

filenames.map(File::new)
    .filter(File::exists)
    .map(f->{
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(f));
        } catch(Exception e) {}
            return Optional.ofNullable(br);
        })
    .filter(Optional::isPresent)
    .map(Optional::get)
    .flatMap(...something that reads the file contents...) // From here, the Stream doesn't content something that gives access to the FileReaders

在做了一些其他的映射等之后,我终于在续集中失去了FileReaders。

我最初认为垃圾收集器能够在需要时执行此操作,但是当 filenames 是长 Stream 时,我经历了操作系统描述符耗尽。

【问题讨论】:

  • 这种映射读者自己的设计会导致问题。更好地处理在一个 lambda 中读取所有文件。首先避免使用Stream<FileReader>
  • @LouisWasserman 你的意思是比这个问题更多?哪个?
  • 嗯,使用一个 try-with-resources 语句来控制读者的整个生命周期等通常是一种很好的做法。
  • 我的意思是您打算以这种方式设计您的流。 (更不用说尝试在中间进行 I/O 操作的复杂性了......)您可能可以通过在您的 flatMap 中执行关闭操作来将其破解,但它仍然是会比尝试使用单个 try-with-resources lambda 更尴尬。
  • 这是一个典型的案例,我更喜欢旧的命令式方法,而不是使用流来打自己的脚。或者,如果您坚持使用流,我会将所有文件处理移至私有方法。

标签: java file java-8 java-stream


【解决方案1】:

我知道这是一个老问题,但我找到了一个非常好的解决方案 here,但我认为它不是这样。

java.nio.file 提供了让您干净利落地执行此操作的方法:

filenames.map(Paths::get)
    // Nicer alternative to File::exists
    .filter(Files::exists)
    // This will automatically close the stream after each file is done reading
    .flatMap(path -> {
        try {
            return Files.lines(path);
        } catch (IOException e) {
            // Seamlessly handles error opening file, no need for filtering
            return Stream.empty();
        }
    })
    .map(/* Do something with each line... */)

【讨论】:

    【解决方案2】:

    所以,如果你只有非二进制文件,你可以使用这样的东西:

    List<String> fileNames = Arrays.asList(
                "C:\\Users\\wowse\\hallo.txt",
                "C:\\Users\\wowse\\bye.txt");
    
    fileNames.stream()
                .map(Paths::get)
                .filter(Files::exists)
                .flatMap(path -> {
                    try {
                        return Files.lines(path);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return null;
                })
                .forEach(System.out::println);
    

    如果您有可以保存在内存中的二进制文件,您可以尝试以下方法。

    fileNames.stream()
                .map(Paths::get)
                .filter(Files::exists)
                .map(path -> {
                    try {
                        return Files.readAllBytes(path);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return null;
                })
                .filter(Objects::nonNull)
                .map(String::new)
                .forEach(System.out::println);
    

    除此之外,我认为您必须使用一些包装类,我可以在其中建议 Map.Entry 或 javafx 中的 Pair,这样您就不必使用任何外部库。

    【讨论】:

    • 就是这样。不要将古老的java.io.File 代码与StreamOptional 结合使用,只需使用为该用例设计的最新NIO API。请注意,在您的第一个示例中,filter(Objects::nonNull) 步骤已过时。它将删除null lines,但这些行永远不会是null。如果flatMap 函数返回null,就像在例外情况下一样,它已经像空流一样被处理。
    【解决方案3】:

    关于使用 FileReader 的一般说明:FileReader 在内部使用 FileInputStream,它覆盖 finalize(),因此是 discouraged to use,因为它对垃圾收集有影响,尤其是在处理大量文件时。

    除非您使用的是 Java 7 之前的 Java 版本,否则您应该改用 java.nio.files API,用

    创建一个 BufferedReader
     Path path = Paths.get(filename);
     BufferedReader br = Files.newBufferedReader(path);
    

    所以你的流管道的开始应该看起来更像

     filenames.map(Paths::get)
              .filter(Files::exists)
              .map(p -> {
            try {
                return Optional.of(Files.newBufferedReader(p));
            } catch (IOException e) {
                return Optional.empty();
            }
        }) 
    

    现在解决您的问题:

    选项 1

    保留原始Reader 的一种方法是使用元组。元组(或它的任何 n 元变体)通常是处理函数应用程序的多个结果的好方法,因为它是在流管道中完成的:

    class ReaderTuple<T> {
       final Reader first;
       final T second;
       ReaderTuple(Reader r, T s){
         first = r;
         second = s;
       }
    }
    

    现在您可以将 FileReader 映射到 Tuple,其中第二项是您当前的流项:

     filenames.map(Paths::get)
      .filter(Files::exists)
      .map(p -> {
            try {
                return Optional.of(Files.newBufferedReader(p));
            } catch (IOException e) {
                return Optional.empty();
            }
        }) 
      .filter(Optional::isPresent)
      .map(Optional::get)
      .flatMap(r -> new ReaderTuple(r, yourOtherItem))
      ....
      .peek(rt -> {
        try { 
          rt.first.close()  //close the reader or use a try-with-resources
        } catch(Exception e){}
       })
      ... 
    

    这种方法的问题是,每当在 flatMap 和 peek 之间的流执行期间发生未经检查的异常时,读取器可能不会关闭。

    选项 2

    使用元组的另一种方法是将需要读取器的代码放在 try-with-resources 块中。这种方法的优点是您可以控制关​​闭所有阅读器。

    示例 1:

     filenames.map(Paths::get)
      .filter(Files::exists)
      .map(p -> {
            try (Reader r = new BufferedReader(new FileReader(p))){
    
                Stream.of(r)
                .... //put here your stream code that uses the stream
    
            } catch (IOException e) {
                return Optional.empty();
            }
        }) //reader is implicitly closed here
     .... //terminal operation here
    

    示例 2:

    filenames.map(Paths::get)
      .filter(Files::exists)
      .map(p -> {
            try {
                return Optional.of(Files.newBufferedReader(p));
            } catch (IOException e) {
                return Optional.empty();
            }
        }) 
     .filter(Optional::isPresent)
     .map(Optional::get)
     .flatMap(reader -> {
       try(Reader r = reader) {
    
          //read from your reader here and return the items to flatten
    
       } //reader is implicitly closed here
      }) 
    

    示例 1 的优点是读者肯定会关闭。示例 2 是安全的,除非您在读取器的创建和可能失败的 try-with-resources 块之间添加更多内容。

    我个人会选择示例 1,并将访问阅读器的代码放在单独的函数中,这样代码的可读性会更好。

    【讨论】:

      【解决方案4】:

      也许更好的解决方案是使用Consumer&lt;FileReader&gt; 来消耗流中的每个元素。

      如果有很多文件,您可能会遇到的另一个问题是所有文件都将同时打开。完成后立即关闭可能会更好。

      假设您将上面的代码更改为采用Consumer&lt;BufferedReader&gt;的方法

      我可能不会为此使用流,但无论如何我们都可以使用流来展示如何使用它。

      public void readAllFiles( Consumer<BufferedReader> consumer){
          Objects.requireNonNull(consumer);
      
          filenames.map(File::new)
                   .filter(File::exists)
                   .forEach(f->{
      
                       try(BufferedReader br = new BufferedReader(new FileReader(f))){
                           consumer.accept(br);
                       } catch(Exception e) {
                           //handle exception
                       }
                   });
      }
      

      通过这种方式,我们确保我们关闭了每个阅读器,并且仍然可以支持用户想要做的任何事情。

      例如,这仍然可以工作

       readAllFiles( br -> System.out.println( br.lines().count()));
      

      【讨论】:

        【解决方案5】:

        只是为了争论(尽管我同意上面的路易斯): 您可以传递原始的 Reader/InputStream (或任何对象,但您提供的案例实际上是有缺陷的编程,因为您可以传递 FileReader 而不是使用 BufferedReader 封装它)使用 common-lang3 @ 987654326@上课。 Jool 也是提供Tuple* 类的有效库。

        例子:

        filenames.map(File::new)
            .filter(File::exists)
            .map(f->{
                BufferedReader br = null;
                FileReader fr = null;
                try {
                    fr = new FileReader(f)
                    br = new BufferedReader(fr);
                    return Optional.of(Pair.of(br,fr)) ;
                } catch(Exception e) {}
                    return Optional.ofNullable(br);
                })
            .filter(Optional::isPresent)
            .map(Optional::get)
            .flatMap( pair -> { 
                try {
                    // do something with br               
                } finally {
                     try {
                         pair.getRight().close();
                     } catch (IOException x ){
                         throw new RuntimeException(x) ;
                     }
                }
            })       
        

        【讨论】:

          猜你喜欢
          • 2019-10-01
          • 2018-09-06
          • 2013-12-13
          • 1970-01-01
          • 1970-01-01
          • 2019-07-17
          • 2010-09-06
          • 1970-01-01
          • 2011-08-01
          相关资源
          最近更新 更多