Java8 Stream文件,如何控制文件的关闭?

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

请注意,我可能无法访问单个FileReader ,例如:

 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 

在做了其他一些映射之后,我终于在续集中丢失了FileReader

我首先想到垃圾收集器能够在需要时执行它,但是当filenames是一个很长的Stream时,我经历过OS描述符耗尽。

关于FileReader的使用的一般说明:FileReader在内部使用FileInputStream来覆盖finalize() ,因此不鼓励使用它因为它对garbarge集合的影响,特别是在处理大量文件时。

除非您在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 { final Reader first; final T second; ReaderTuple(Reader r, T s){ first = r; second = s; } } 

现在,您可以将FileReader映射到元组,第二个项目是您当前的流项目:

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

这种方法的问题是,无论何时在流执行期间发生未经检查的exception发生在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,并将访问读取器的代码放在一个单独的函数中,以便代码更易读。

也许更好的解决方案是使用Consumer来使用流中的每个元素。

如果有大量文件,您可能遇到的另一个问题是文件将同时打开。 一旦完成关闭每一个可能会更好。

假设您将上面的代码更改为采用Consumer

我可能不会为此使用流,但我们可以使用一个来显示如何使用它。

 public void readAllFiles( Consumer 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())); 

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

 List 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); 

除此之外,我认为你必须使用一些包装类,我可以从javafx建议Map.EntryPair ,这样你就不必使用任何外部库。

只是为了争论(虽然我同意上面的路易斯):你可以传递原始的Reader / InputStream (或任何对象,但你提供的情况实际上是有缺陷的编程,因为你可以传递FileReader而不是封装它与BufferedReader )使用common-lang3 Pair Class。 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) ; } } })