假设我有一个Java8 Stream<FileReader>
并且我将该流用于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
时,我遇到操作系统描述符耗尽。
答案 0 :(得分:5)
关于FileReader使用的一般说明:FileReader在内部使用了一个覆盖finalize()
的FileInputStream,因此discouraged to use因为它对garbarge集合的影响,特别是在处理大量文件时。 / p>
除非你在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();
}
})
现在问题:
保留原始Reader
的一种方法是使用元组。元组(或其任何n元变体)通常是处理函数应用程序的多个结果的好方法,因为它在流管道中完成:
class ReaderTuple<T> {
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){}
})
...
这种方法的问题是,无论何时在流执行期间发生未经检查的异常发生在flatMap和peek之间,读者可能都不会被关闭。
使用元组的另一种方法是将需要阅读器的代码放在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,并将访问阅读器的代码放在一个单独的函数中,以便代码更易读。
答案 1 :(得分:3)
或许更好的解决方案是使用Consumer<FileReader>
来使用流中的每个元素。
如果有大量文件,您可能遇到的另一个问题是文件将同时打开。一旦完成,最好关闭每一个。
假设您将上面的代码更改为采用Consumer<BufferedReader>
我可能不会为此使用流,但我们可以使用一个来显示如何使用它。
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()));
答案 2 :(得分:1)
因此,如果您只有非二进制文件,则可以使用以下内容:
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);
除此之外,我认为您必须使用一些包装类,我可以在javafx中建议Map.Entry
或Pair
,这样您就不必使用任何外部库。< / p>
答案 3 :(得分:0)
我知道这是一个古老的问题,但是有一个非常不错的解决方案,我发现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... */)
答案 4 :(得分:-1)
只是为了争论(虽然我同意路易斯的意见):
您可以传递原始Reader
/ InputStream
(或任何对象,但您提供的案例实际上是有缺陷的编程,因为您可以传递FileReader
而不是使用{{1}封装它使用common-lang3 BufferedReader
类。 Jool也是一个提供Pair
类的有效库。
示例:
Tuple*