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.valueInteger,它是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,我们需要传递一个ComparatorcomparingByValue()**:

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上找到。