Java 对映射排序

Java 对映射排序,用户希望根据键或值对 Map 排序,使用 Map.Entry 接口新增的静态方法。Map 接口始终包含一个称为 Map.Entry 的公共静态内部接口(public, static, inner interface),它表示一个键值对。

Java 对映射排序 问题描述

用户希望根据键或值对 Map 排序。

Java 对映射排序 解决方案

使用 Map.Entry 接口新增的静态方法。

Java 对映射排序 具体实例

Map 接口始终包含一个称为 Map.Entry 的公共静态内部接口(public, static, inner interface),它表示一个键值对。Map 接口定义的 entrySet 方法返回 Map.Entry 元素的 Set。在 Java 8 之前,getKeygetValueMap.Entry 接口两种最常用的方法,二者分别返回与某个条目对应的键和值。
Java 8 为 Map.Entry 接口引入了一些新的静态方法,如表 4-1 所示。
表4-1 Map.Entry接口新增的静态方法(参见Java 8文档)

方法 描述
comparingByKey() 返回一个比较器,它根据键的自然顺序比较 Map.Entry
comparingByKey(Comparator<? super K> cmp) 返回一个比较器,它使用给定的 Comparator 并根据键比较 Map.Entry
comparingByValue() 返回一个比较器,它根据值的自然顺序比较 Map.Entry
comparingByValue(Comparator<? super V> cmp) 返回一个比较器,它使用给定的 Comparator 并根据值比较 Map.Entry

我们以创建单词长度与单词数量的 Map 为例,演示上述方法的用法(例 4-18)。所有 Unix 系统的 usr/share/dict/words 目录中都包含一个文件,它收录了《韦氏词典(第 2 版)》的内容,每个单词在文件中占据一行。Files.lines 方法可用于读取文件并生成一个包含这些行的字符串流。此时,流包含词典中的所有单词。

例 4-18 将词典文件读入 Map

System.out.println("\nNumber of words of each length:");
try (Stream<String> lines = Files.lines(dictionary)) {
    lines.filter(s -> s.length() > 20)
        .collect(Collectors.groupingBy(
            String::length, Collectors.counting()))
        .forEach((len, num) -> System.out.printf("%d: %d%n", len, num));
} catch (IOException e) {
    e.printStackTrace();
}

本例的详细讨论请参见范例文件处理,目前可以这样理解。

  • 文件在 try-with-resources 代码块内读取。由于 Stream 接口实现了 AutoCloseabletry 代码块执行完毕后,Java 对 Stream 调用 close 方法,然后对 File 调用 close 方法。
  • 筛选器只筛出长度至少为 20 个字符的单词,以供进一步处理。
  • Collectors.groupingBy 方法传入 Function 作为第一个参数,表示分类器(classifier)。在本例中,分类器是每个字符串的长度。如果 groupingBy 方法只传入一个参数,则结果为 Map,其中键为分类器的值,值为匹配分类器的元素列表。这种情况下,groupingBy(String::length) 将返回 Map<Integer,List<String>>,其中键为单词长度,值为该长度的单词列表。
  • 在本例中,双参数形式的 groupingBy 方法传入另一个 Collector,它称为下游收集器(downstream collector),用于对单词列表进行后期处理。这种情况下,groupingBy 方法的返回类型是 Map<Integer,Long>,其中键为单词长度,值为词典中该长度的单词数量。

例 4-18 的输出结果如下:

Number of words of each length:
21: 82
22: 41
23: 17
24: 5

换言之,有 82 个单词的长度为 21,41 个单词的长度为 22,17 个单词的长度为 23,5 个单词的长度为 243
3根据记录,这 5 个最长的单词为 formaldehydesulphoxylate(甲醛次硫酸氢钠)、pathologicopsychological(病理心理学)、scientificophilosophical(科学哲学)、tetraiodophenolphthalein(四碘酚酞)以及 thyroparathyroidectomize(甲状腺甲状腺切除术)。希望拼写检查工具可以识别这些单词。

不难看到,程序按单词长度的升序打印映射中的单词。如果希望按降序打印,可以使用 Map.Entry 接口定义的 comparingByKey 方法,如例 4-19 所示。

例 4-19 根据键对映射排序

System.out.println("\nNumber of words of each length (desc order):");
try (Stream<String> lines = Files.lines(dictionary)) {
    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: %2d words%n",
            e.getKey(), e.getValue()));
} catch (IOException e) {
    e.printStackTrace();
}

返回 Map<Integer,Long> 之后,程序将提取 entrySet 并产生一个流。Stream.sorted 方法使用提供的比较器生成经过排序的流。
在本例中,comparingByKey 方法返回一个根据键进行排序的比较器。如果希望以键的相反顺序排序,可以使用 comparingByKey 方法的重载形式,它传入比较器作为参数。

Stream.sorted 方法生成一个新的排序流,它不对源数据进行修改。换言之,原始 Map 不受影响。

例 4-19 的输出结果如下:

Number of words of each length (desc order):
Length 24:  5 words
Length 23: 17 words
Length 22: 41 words
Length 21: 82 words

表 4-1 列出的 comparingByValue 方法,用法与 comparingByKey 类似。

赞(1)

评论 抢沙发

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

Java 实例