Java 文件处理

Java 文件处理,用户希望使用流来处理文本文件的内容,使用 java.io.BufferedReaderjava.nio.file.Files 类定义的静态方法 lines,以流的形式返回文件内容。在所有基于 FreeBSD 的 UNIX 系统(包括 macOS)中,/usr/share/dict/ 文件夹都包含《韦氏国际英语词典(第 2 版)》。

Java 文件处理 问题描述

用户希望使用流来处理文本文件的内容。

Java 文件处理 解决方案

使用 java.io.BufferedReaderjava.nio.file.Files 类定义的静态方法 lines,以流的形式返回文件内容。

Java 文件处理 具体实例

在所有基于 FreeBSD 的 UNIX 系统(包括 macOS)中,/usr/share/dict/ 文件夹都包含《韦氏国际英语词典(第 2 版)》。web2 文件收录了大约 23 万个单词,每个单词在文件中占据一行。
假设我们希望查找词典中最长的 10 个单词。为此,我们首先使用 Files.lines 方法,将单词作为字符串流进行检索,然后执行 mapfilter 等常规的流处理操作。相关示例如例 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 接口新增的静态方法 comparingByKeycomparingByValue,二者均传入可选的 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 也随之关闭。

赞(1)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

Java 实例