Files
工具类首次在Java7中引入,作为NIO的一部分。JDK8 API添加了一些额外的方法,它们可以将文件用于函数式数据流。让我们深入探索一些代码示例。
列出文件
Files.list
方法将指定目录的所有路径转换为数据流,便于我们在文件系统的内容上使用类似filter
和sorted
的流操作。
try (Stream<Path> stream = Files.list(Paths.get(""))) {
String joined = stream
.map(String::valueOf)
.filter(path -> !path.startsWith("."))
.sorted()
.collect(Collectors.joining("; "));
System.out.println("List: " + joined);
}
上面的例子列出了当前工作目录的所有文件,之后将每个路径都映射为它的字符串表示。之后结果被过滤、排序,最后连接为一个字符串。如果你还不熟悉函数式数据流,你应该阅读 数据流 。
你可能已经注意到,数据流的创建包装在try-with
语句中。数据流实现了AutoCloseable
,并且这里我们需要显式关闭数据流,因为它基于IO操作。
返回的数据流是
DirectoryStream
的封装。如果需要及时处理文件资源,就应该使用try-with
结构来确保在流式操作完成后,数据流的close
方法被调用。
查找文件
下面的例子演示了如何查找在目录及其子目录下的文件:
Path start = Paths.get("");
int maxDepth = 5;
try (Stream<Path> stream = Files.find(start, maxDepth, (path, attr) ->
String.valueOf(path).endsWith(".js"))) {
String joined = stream.sorted().map(String::valueOf).collect(Collectors.joining("; "));
System.out.println("Found: " + joined);
}
find
方法接受三个参数:目录路径start
是起始点,maxDepth
定义了最大搜索深度。第三个参数是一个匹配谓词,定义了搜索的逻辑。上面的例子中,我们搜索了所有JavaScirpt文件(以.js
结尾的文件名)。
我们可以使用Files.walk
方法来完成相同的行为。这个方法会遍历每个文件,而不需要传递搜索谓词。
Path start = Paths.get("");
int maxDepth = 5;
try (Stream<Path> stream = Files.walk(start, maxDepth)) {
String joined = stream
.map(String::valueOf)
.filter(path -> path.endsWith(".js"))
.sorted()
.collect(Collectors.joining("; "));
System.out.println("walk(): " + joined);
}
这个例子中,我们使用了流式操作filter
来完成和上个例子相同的行为。
读写文件
将文本文件读到内存,以及向文本文件写入字符串在Java 8 中是简单的任务。不需要再去摆弄读写器了。Files.readAllLines
从指定的文件把所有行读进字符串列表中。你可以简单地修改这个列表,并且将它通过Files.write
写到另一个文件中:
List<String> lines = Files.readAllLines(Paths.get("res/nashorn1.js"));
lines.add("print('foobar');");
Files.write(Paths.get("res/nashorn1-modified.js"), lines);
要注意这些方法对内存并不十分高效,因为整个文件都会读进内存。文件越大,所用的堆区也就越大。
你可以使用Files.lines
方法来作为内存高效的替代。这个方法读取每一行,并使用函数式数据流来对其流式处理,而不是一次性把所有行都读进内存。
try (Stream<String> stream = Files.lines(Paths.get("res/nashorn1.js"))) {
stream
.filter(line -> line.contains("print"))
.map(String::trim)
.forEach(System.out::println);
}
如果你需要更多的精细控制,你需要构造一个新的BufferedReader
来代替:
Path path = Paths.get("res/nashorn1.js");
try (BufferedReader reader = Files.newBufferedReader(path)) {
System.out.println(reader.readLine());
}
或者,你需要写入文件时,简单地构造一个BufferedWriter
来代替:
Path path = Paths.get("res/output.js");
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
writer.write("print('Hello World');");
}
BufferedReader
也可以访问函数式数据流。lines
方法在它所有行上面构建数据流:
Path path = Paths.get("res/nashorn1.js");
try (BufferedReader reader = Files.newBufferedReader(path)) {
long countPrints = reader
.lines()
.filter(line -> line.contains("print"))
.count();
System.out.println(countPrints);
}
下一节:如何预防 Java 中著名的 NullPointerException 异常?这是每个 Java 初学者迟早会问到的关键问题之一。而且中级和高级程序员也在时时刻刻规避这个错误。其是迄今为止 Java 以及很多其他编程语言中最流行的一种错误。