Java8 Stream of files, how to control the closing of files?

后端 未结 5 2133
陌清茗
陌清茗 2021-01-06 02:39

Suppose I have a Java8 Stream and that I use that stream to map and such, how can I control the closing of the FileReader

5条回答
  •  梦毁少年i
    2021-01-06 03:22

    A general note on the use of FileReader: FileReader uses internally a FileInputStream which overrides finalize() and is therefore discouraged to use beacause of the impact it has on garbarge collection especially when dealing with lots of files.

    Unless you're using a Java version prior to Java 7 you should use the java.nio.files API instead, creating a BufferedReader with

     Path path = Paths.get(filename);
     BufferedReader br = Files.newBufferedReader(path);
    

    So the beginning of your stream pipeline should look more like

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

    Now to your problem:

    Option 1

    One way to preserve the original Reader would be to use a Tuple. A tuple (or any n-ary variation of it) is generally a good way to handle multiple results of a function application, as it's done in a stream pipeline:

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

    Now you can map the FileReader to a Tuple with the second item being your current stream item:

     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){}
       })
      ... 
    

    Problem with that approach is, that whenever an unchecked exception occurrs during stream execution betweem the flatMap and the peek, the readers might not be closed.

    Option 2

    An alternative to use a tuple is to put the code that requires the reader in a try-with-resources block. This approach has the advantage that you're in control to close all readers.

    Example 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
    

    Example 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
      }) 
    

    Example 1 has the advantage that the reader gets certainly closed. Example 2 is safe unless you put something more between the the creation of the reader and the try-with-resources block that may fail.

    I personally would go for Example 1, and put the code that is accessing the reader in a separate function so the code is better readable.

提交回复
热议问题