Java NIO中的Files类(java.nio.file.Files)提供了多种操作文件系统中文件的方法。Files 最早于 Java 7 中的 NIO(non-blocking I/O) 模块中引入,在 Java 8 中加入了一些提供对 Stream API 支持的方法。

项目所在目录的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
C:\Users\Zwx\Projects\CLTest>tree
文件夹 PATH 列表
卷序列号为 EC46-6FE7
C:.
├─.idea
│ ├─inspectionProfiles
│ └─libraries
├─src
│ ├─main
│ │ ├─java
│ │ │ └─net
│ │ │ └─zwx
│ │ └─resources
│ └─test
│ └─java
└─target
├─classes
│ └─net
│ └─zwx
└─generated-sources
└─annotations

列出目录内容

Files.list() 提供了类似 Linux 下 ls 命令的功能,它将给定路径所对应文件夹中文件的路径 (path) 以 stream 的形式返回,以便于使用相关 API 对文件系统的信息进行操作。

1
2
3
4
5
6
7
8
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);
}

输出:

1
List: CLTest.iml; pom.xml; src; target

Stream 本身实现了资源自动关闭的接口 AutoCloseable,因此可以将 stream 的创建过程放置于 try 后的括号内(try-with-resource),在 try 代码块执行完毕后可自动关闭。

查找文件

Files.find(Path start,int maxDepth,BiPredicate matcher,FileVisitOption… options) 则提供了类似 Linux 下 find 命令的功能,以给定的路径 start 为开始查找的点,最大查找层级深度为 maxDepth,查找所有符合 matcher 条件的文件,options 则是可选的文件遍历时的选项。

1
2
3
4
5
6
7
8
9
10
Path start = Paths.get("");
int maxDepth = 5;
try (Stream<Path> stream = Files.find(start, maxDepth, (path, attr) ->
String.valueOf(path).endsWith(".q"))) {
String joined = stream
.sorted()
.map(String::valueOf)
.collect(Collectors.joining("; "));
System.out.println("Found: " + joined);
}

输出:

1
Found: src\main\resources\info.q; target\classes\info.q

要实现查找文件,还可以通过另一个 Java 8 引入的 API Files.walk(Path start,int maxDepth) 实现。 walk() 以 start 为起始点,最大遍历层级深度为 maxDepth ,遍历所有文件。

1
2
3
4
5
6
7
8
9
10
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(".q"))
.sorted()
.collect(Collectors.joining("; "));
System.out.println("Found: " + joined);
}

输出结果同 find() API。

读写文件

简单方案

自 Java 8 起,读写文本文件的流程终于变得精简了,不再需要在读写的过程中提供各种 reader 和 writer,Files.readAllLines() API 将文本文件的所有内容读入到一个 List 中,然后可以通过 Files.write() 将 List 中的文件写入到另一个文件中。

1
2
3
List<String> lines = Files.readAllLines(Paths.get("info.q"));
lines.add("another line.");
Files.write(Paths.get("info.out"), lines);

需要注意的是这两个方法因为需要将整个文件内容都读入到内存中进行操作,因此“内存效率”并不高,文件内容越大,需要使用的堆大小则越多。

改进方案

在注重“内存效率”的场景下, Files.lines() 无疑是更好的解决方案,lines() 将文本文件的内容按行读取到内存中,返回 Stream 对象,利用串行 Stream 对元素逐个处理的特性,可以按行处理文本内容,而不需要过大的内存开销。

1
2
3
4
5
try (Stream<String> stream = Files.lines(Paths.get("info.q"))) {
stream.filter(line -> line.contains("print"))
.map(String::trim)
.forEach(System.out::println);
}

Files.newBufferedReader(path) 则对文件读取操作提供了更细致方法,BufferedReader 同样实现了 Stream 相关接口:

1
2
3
4
5
6
7
try (BufferedReader bufferedReader = Files.newBufferedReader(Paths.get("info.q"))){
System.out.println(bufferedReader.readLine());
long countPrints = bufferedReader
.lines()
.filter(line -> line.contains("print"))
.count();
}

要将文本内容写入到文件中,则只需要一个 BufferedWriter :

1
2
3
4
Path path = Paths.get("info.out");
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
writer.write("print('Hello World');");
}

Java 8 提供了三种简单地方法来读取文本文件中的内容,使得对文本的处理变得更加方便。

参考资料