1. 概述

本教程我们将学习如何实现HashMap按值或按键排序

我们将介绍下面几种方式:

  • TreeMap
  • ArrayList 和 Collections.sort()
  • TreeSet
  • 使用 Stream API
  • 利用 Guava library

2. 使用 TreeMap

我们知道, TreeMap 中的元素默认按照keys的自然排序排列。如果我们希望按key对元素进行排序时,这是一个不错的解决方案。因此,我们的想法是把 HashMap 中的所有数据,加载到 TreeMap 中 。

首先我们定义一个 HashMap 并初始化数据:

    Map<String, Employee> map = new HashMap<>();
    
    Employee employee1 = new Employee(1L, "Mher");
    map.put(employee1.getName(), employee1);
    Employee employee2 = new Employee(22L, "Annie");
    map.put(employee2.getName(), employee2);
    Employee employee3 = new Employee(8L, "John");
    map.put(employee3.getName(), employee3);
    Employee employee4 = new Employee(2L, "George");
    map.put(employee4.getName(), employee4);

下面是 Employee 类的定义,注意,它实现了 Comparable 接口:

    public class Employee implements Comparable<Employee> {
    
        private Long id;
        private String name;
    
        // 构造函数, getters, setters
    
        // 重新 equals 和 hashCode 方法
        @Override
        public int compareTo(Employee employee) {
            return (int)(this.id - employee.getId());
        }
    }

然后通过 TreeMap 的构造函数复制数据:

TreeMap<String, Employee> sorted = new TreeMap<>(map);

或者使用 putAll 方法:

TreeMap<String, Employee> sorted = new TreeMap<>();
sorted.putAll(map);

最后打印输出,以验证是否按键名排序:

Annie=Employee{id=22, name='Annie'}
George=Employee{id=2, name='George'}
John=Employee{id=8, name='John'}
Mher=Employee{id=1, name='Mher'}

可以看到,keys是按自然顺序排列。

3. 使用 ArrayList

当然,我们可以借助 ArrayList 对 map 的元素进行排序。 与前面的方法的主要区别在于我们不维护 Map 接口。

3.1. 按键名排序

将所有的 key 加载到 ArrayList中:

List<String> employeeByKey = new ArrayList<>(map.keySet());
Collections.sort(employeeByKey);

排序后的结果:

[Annie, George, John, Mher]

3.2. 按值排序

现在,如果我们想根据 Employee 对象的 id 字段进行排序怎么办?

首先,复制数据到 list 中:

List<Employee> employeeById = new ArrayList<>(map.values());

然后直接排序即可:

Collections.sort(employeeById);

能这么做的原因是因为,Employee 实现了 Comparable 接口。否者在调用 Collections.sort 的时候,我们需要手动传一个 comparator

打印,验证排序是否正确:

[Employee{id=1, name='Mher'}, 
Employee{id=2, name='George'}, 
Employee{id=8, name='John'}, 
Employee{id=22, name='Annie'}]

可以看到,结果是按 id 字段排序。

4. 利用 TreeSet

如果排序结果需要去掉重复元素,那么推荐使用TreeSet。

让我们在初始化map中添加一些重复数据:

Employee employee5 = new Employee(1L, "Mher");
map.put(employee5.getName(), employee5);
Employee employee6 = new Employee(22L, "Annie");
map.put(employee6.getName(), employee6);

4.1. 按键名排序

按 key 排序:

SortedSet<String> keySet = new TreeSet<>(map.keySet());

打印 keySet

[Annie, George, John, Mher]

可以看到排序后的 keys 中出现没有重复数据。

4.2. 按值排序

SortedSet<Employee> values = new TreeSet<>(map.values());

结果:

[Employee{id=1, name='Mher'}, 
Employee{id=2, name='George'}, 
Employee{id=8, name='John'}, 
Employee{id=22, name='Annie'}]

可以看到,输出结果中没有出现重复数据。这是因为我们的自定义对象 Employee 重写了 equals 和 hashCode 方法。

5. 使用 Lambda 表达式和 Stream API

Since the Java 8, we can use the Stream API and lambda expressions to sort the map. All we need is to call the sorted method over the map's stream pipeline.

5.1. 按键名排序

To sort by key, we use the comparingByKey comparator:

map.entrySet()
  .stream()
  .sorted(Map.Entry.<String, Employee>comparingByKey())
  .forEach(System.out::println);

The final forEach stage prints out the results:

Annie=Employee{id=22, name='Annie'}
George=Employee{id=2, name='George'}
John=Employee{id=8, name='John'}
Mher=Employee{id=1, name='Mher'}

By default, the sorting mode is ascending.

5.2. 按值排序

Of course, we can sort by the Employee objects as well:

map.entrySet()
  .stream()
  .sorted(Map.Entry.comparingByValue())
  .forEach(System.out::println);

As we see, the code above prints out a map sorted by the id fields of Employee objects:

Mher=Employee{id=1, name='Mher'}
George=Employee{id=2, name='George'}
John=Employee{id=8, name='John'}
Annie=Employee{id=22, name='Annie'}

Additionally, we can collect the results into a new map:

Map<String, Employee> result = map.entrySet()
  .stream()
  .sorted(Map.Entry.comparingByValue())
  .collect(Collectors.toMap(
    Map.Entry::getKey, 
    Map.Entry::getValue, 
    (oldValue, newValue) -> oldValue, LinkedHashMap::new));

Note that we collected our results into a LinkedHashMap. By default, Collectors.toMap returns a new HashMap, but as we know, HashMap doesn't guarantee iteration order, while LinkedHashMap does.

6. 使用第三方库 - Guava

Lastly, a library that allows us to sort the HashMap is Guava. Before we start, it'll be useful to check our write-up about maps in Guava.

First, let's declare an Ordering as we want to sort our map by Employee's Id field:

Ordering naturalOrdering = Ordering.natural()
  .onResultOf(Functions.forMap(map, null));

Now, all we need is to use ImmutableSortedMap to illustrate the results:

ImmutableSortedMap.copyOf(map, naturalOrdering);

And once again, the output is a map ordered by the id field:

Mher=Employee{id=1, name='Mher'}
George=Employee{id=2, name='George'}
John=Employee{id=8, name='John'}
Annie=Employee{id=22, name='Annie'}

7. 总结

In this article, we reviewed a number of ways to sort a HashMap by key or by value.

And we took a close look at how we can do this when the attribute is a custom class by implementing Comparable.

Finally, as always, the code used during the discussion can be found over on GitHub.