Java 文件处理,用户希望使用流来处理文本文件的内容,使用 java.io.BufferedReader
或 java.nio.file.Files
类定义的静态方法 lines
,以流的形式返回文件内容。在所有基于 FreeBSD 的 UNIX 系统(包括 macOS)中,/usr/share/dict/ 文件夹都包含《韦氏国际英语词典(第 2 版)》。
Java 文件处理 问题描述
用户希望使用流来处理文本文件的内容。
Java 文件处理 解决方案
使用 java.io.BufferedReader
或 java.nio.file.Files
类定义的静态方法 lines
,以流的形式返回文件内容。
Java 文件处理 具体实例
在所有基于 FreeBSD 的 UNIX 系统(包括 macOS)中,/usr/share/dict/ 文件夹都包含《韦氏国际英语词典(第 2 版)》。web2 文件收录了大约 23 万个单词,每个单词在文件中占据一行。
假设我们希望查找词典中最长的 10 个单词。为此,我们首先使用 Files.lines
方法,将单词作为字符串流进行检索,然后执行 map
、filter
等常规的流处理操作。相关示例如例 7-1 所示。
例 7-1 在 web2 文件中查找最长的 10 个单词
try (Stream<String> lines = Files.lines(Paths.get("/usr/share/dict/web2")) {
lines.filter(s -> s.length() > 20)
.sorted(Comparator.comparingInt(String::length).reversed())
.limit(10)
.forEach(w -> System.out.printf("%s (%d)%n", w, w.length()));
} catch (IOException e) {
e.printStackTrace();
}
在本例中,filter
方法中的谓词筛掉了长度不足 20 个字符的单词,sorted
方法按长度对这些单词做降序排序,limit
方法在获取前 10 个单词后终止程序,然后打印这些单词。由于流是在 try-with-resources
代码块中打开的,当 try
代码块完成时,系统将自动关闭流与词典文件。
流与
AutoCloseable
接口
Stream
接口继承自BaseStream
,而它是AutoCloseable
的子接口。因此,可以在 Java 7 新增的try-with-resources
代码块中使用流。try 代码块执行完毕后,系统将自动调用close
方法。它不仅会关闭流,还会调用流的流水线(stream pipeline)中的任何close
处理程序以释放资源。
到目前为止,我们尚未接触try-with-resources
包装器,因为之前讨论的流是从集合或在内存中生成的。而在本范例中,流是基于文件的,因此try-with-resources
能确保词典文件也被关闭。
执行例 7-1 中的代码,结果如例 7-2 所示。
例 7-2 词典中最长的 10 个单词
formaldehydesulphoxylate (24)
pathologicopsychological (24)
scientificophilosophical (24)
tetraiodophenolphthalein (24)
thyroparathyroidectomize (24)
anthropomorphologically (23)
blepharosphincterectomy (23)
epididymodeferentectomy (23)
formaldehydesulphoxylic (23)
gastroenteroanastomosis (23)
如上所示,词典中有 5 个单词的长度为 24 个字符。结果之所以按字母顺序显示,只是因为原始文件中的单词是按字母顺序排序的。在例 7-1 中,如果为 sorted
方法的 Comparator
参数添加一个 thenComparing
子句,就能调整等长单词的排序方式了。
在 5 个长度为 24 个字符的单词之后,是 5 个长度为 23 个字符的单词,其中大部分单词来自医学领域。3
3好在 blepharosphincterectomy(眼轮匝肌切除术)与其字面意思无关,这是一个与减轻角膜上眼睑压力有关的单词。听起来很头疼?其实可能更头疼。
如果将 Collectors.counting
作为下游收集器,就能确定词典中每种长度的单词数量,如例 7-3 所示。
例 7-3 确定每种长度的单词数量(升序排序)
try (Stream<String> lines = Files.lines(Paths.get("/usr/share/dict/web2"))) {
lines.filter(s -> s.length() > 20)
.collect(Collectors.groupingBy(String::length, Collectors.counting()))
.forEach((len, num) -> System.out.println(len + ": " + num));
}
上述代码使用收集器 groupingBy
创建一个 Map
,其中键为单词长度,值为每种长度的单词数量。代码的执行结果如下:
21: 82
22: 41
23: 17
24: 5
上述输出虽然提供了部分信息,但并非特别有用。且结果按升序排序,这也可能不满足我们的要求。
另一种方案是采用 Map.Entry
接口新增的静态方法 comparingByKey
和 comparingByValue
,二者均传入可选的 Comparator
(相关讨论请参见范例对映射排序)。如例 7-4 所示,通过比较器 reverseOrder
进行排序时,将返回自然顺序的相反顺序。
例 7-4 确定每种长度的单词数量(降序排序)
try (Stream<String> lines = Files.lines(Paths.get("/usr/share/dict/web2"))) {
Map<Integer, Long> map = lines.filter(s -> s.length() > 20)
.collect(Collectors.groupingBy(String::length, Collectors.counting()));
map.entrySet().stream()
.sorted(Map.Entry.comparingByKey(Comparator.reverseOrder()))
.forEach(e -> System.out.printf("Length %d: %d words%n",
e.getKey(), e.getValue()));
}
程序的执行结果如下:
Length 24: 5 words
Length 23: 17 words
Length 22: 41 words
Length 21: 82 words
如果数据源不是文件,也可以使用 BufferedReader
类新增的 lines
方法(这种情况下,lines
是一个实例方法)。采用 BufferedReader.lines
方法对例 7-4 改写,结果如例 7-5 所示。
例 7-5
BufferedReader.lines
方法的应用
try (Stream<String> lines =
new BufferedReader(
new FileReader("/usr/share/dict/words")).lines()) {
// 其余代码与例7-4相同
}
需要再次强调的是,由于 Stream
接口实现了 AutoCloseable
接口,当 try-with-resources
代码块关闭流时,底层 BufferedReader
也随之关闭。