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 之前,getKey
和 getValue
是 Map.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
接口实现了AutoCloseable
,try
代码块执行完毕后,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
类似。