1. 概述
在需要管理键值关联时,映射是一种常见的数据类型。其中,链式哈希映射因其保留插入顺序而广受欢迎。然而,在许多实际场景中,我们可能需要根据值而非键对链式哈希映射的元素进行排序。
本教程将探讨如何在Java中根据值对链式哈希映射进行排序。
2. 根据值排序
链式哈希映射的默认行为是基于插入顺序维护元素顺序,这对于跟踪元素添加到映射中的顺序很有用。但是,按值排序是另一种需求,即我们可能希望根据键关联的值,以升序或降序排列条目。
下面是一个例子。假设我们有一个名为MY_MAP
的链式哈希映射:
static LinkedHashMap<String, Integer> MY_MAP = new LinkedHashMap<>();
static {
MY_MAP.put("key a", 4);
MY_MAP.put("key b", 1);
MY_MAP.put("key c", 3);
MY_MAP.put("key d", 2);
MY_MAP.put("key e", 5);
}
如上所示,我们通过一个静态块初始化了MY_MAP
,其值为整数。目标是根据值对映射进行排序,并得到与EXPECTED_MY_MAP
相等的新链式哈希映射:
static LinkedHashMap<String, Integer> EXPECTED_MY_MAP = new LinkedHashMap<>();
static{
EXPECTED_MY_MAP.put("key b", 1);
EXPECTED_MY_MAP.put("key d", 2);
EXPECTED_MY_MAP.put("key c", 3);
EXPECTED_MY_MAP.put("key a", 4);
EXPECTED_MY_MAP.put("key e", 5);
}
接下来,我们将探讨几种解决此问题的方法,并使用单元测试断言来验证每个解决方案。
3. 使用Collections.sort()
方法
首先,我们来看看如果我们的Java版本低于Java 8,如何解决这个问题。
链式哈希映射的entrySet()
方法在保持原始顺序的同时提供对所有条目的访问。
我们还可以利用Collections.sort()
方法,它可以按照给定的Comparator
对对象集合进行排序。
让我们先看一下解决方案:
List<Map.Entry<String, Integer>> entryList = new ArrayList<>(MY_MAP.entrySet());
Collections.sort(entryList, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
LinkedHashMap<String, Integer> result = new LinkedHashMap<>();
for (Map.Entry<String, Integer> e : entryList) {
result.put(e.getKey(), e.getValue());
}
assertEquals(EXPECTED_MY_MAP, result);
快速浏览一下代码的工作原理。
首先,我们将entrySet()
的结果包装成一个List
。然后,我们创建了一个匿名Comparator
,用于根据值对条目进行排序,并将其传递给Collections.sort()
方法。最后,我们创建一个新的链式哈希映射对象,并将排序后的条目放入其中。
4. 使用forEachOrdered()
Java 8引入的流API(Stream API)为我们提供了方便地操作集合的方式。因此,如果我们使用的Java版本为8或更高版本,我们可以使用流API从原始映射中填充一个空的链式哈希映射,以获得按值排序的新条目:
LinkedHashMap<String, Integer> result = new LinkedHashMap<>();
MY_MAP.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue())
.forEachOrdered(entry -> result.put(entry.getKey(), entry.getValue()));
assertEquals(EXPECTED_MY_MAP, result);
如您所见,使用流API的解决方案更加流畅且紧凑。
值得注意的是,Map.Entry
支持comparingByValue()
方法。顾名思义,它返回一个根据值比较条目的Comparator
。
由于我们示例中的Entry.value
是Integer
,它是Comparable
的,所以我们可以直接调用comparingByValue()
。
5. 使用collect()
方法
另一种更简洁的方法是利用collect()
方法一次创建映射并积累排序条目:
LinkedHashMap<String, Integer> result = MY_MAP.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue())
.collect(LinkedHashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()), Map::putAll);
assertEquals(EXPECTED_MY_MAP, result);
collect()
方法是这种方法的关键,它接受三个参数:
- 提供器(
LinkedHashMap::new
) - 提供一个累积结果的新容器(LinkedHashMap
) - 集累器
(map, entry) -> map.put(entry.getKey(), entry.getValue())
- 此函数应用于流中的每个元素,并将每个条目添加到累积的LinkedHashMap
中 - 合并器 (
Map::putAll
) - 在并行处理中,它合并由多个累积器更新的容器。在这种情况下,由于流是顺序处理的,所以这无关紧要。
因此,collect()
方法将排序条目积累到一个新的LinkedHashMap
中。
6. 当值不实现Comparable
时
我们已经看到了如何对MY_MAP
按值排序。由于Integer
值是Comparable
的,当我们使用流API时,可以直接调用sorted(Map.Entry.comparingByValue())
。
但是,**如果值不实现Comparable
,我们需要传递一个Comparator
给comparingByValue()
**:
class Player {
private String name;
private Integer score = 0;
public Player(String name, Integer score) {
this.name = name;
this.score = score;
}
// ... hashcode, equals, getters methods are omitted ...
}
代码显示,Player
类没有实现Comparable
。现在,让我们初始化一个LinkedHashMap<String, Player>
:
static LinkedHashMap<String, Player> PLAYERS = new LinkedHashMap<>();
static {
PLAYERS.put("player a", new Player("Eric", 9));
PLAYERS.put("player b", new Player("Kai", 7));
PLAYERS.put("player c", new Player("Amanda", 20));
PLAYERS.put("player d", new Player("Kevin", 4));
}
假设我们想要根据玩家分数对PLAYERS
进行排序,并得到一个新的LinkedHashMap
:
static LinkedHashMap<String, Player> EXPECTED_PLAYERS = new LinkedHashMap<>();
static {
EXPECTED_PLAYERS.put("player d", new Player("Kevin", 4));
EXPECTED_PLAYERS.put("player b", new Player("Kai", 7));
EXPECTED_PLAYERS.put("player a", new Player("Eric", 9));
EXPECTED_PLAYERS.put("player c", new Player("Amanda", 20));
}
那么接下来,我们看看如何实现:
LinkedHashMap<String, Player> result = PLAYERS.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue(Comparator.comparing(Player::getScore)))
.collect(LinkedHashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()), Map::putAll);
assertEquals(EXPECTED_PLAYERS, result);
在这种情况下,我们在comparingByValue()
方法中使用了Comparator.comparing(Player::getScore)
。
这个构造通过实例方法引用生成了一个Comparator
,具体比较Player
对象的score
字段。
7. 总结
在这篇教程中,我们探讨了多种按值对链式哈希映射进行排序的方法。我们还讨论了在值不实现Comparable
接口的情况下排序实现的问题。
如往常一样,所有示例的完整源代码可在GitHub上找到。