1. 引言
在 Java 集合框架中,Map 是最常用的接口之一,用于存储键值对。然而,标准的 HashMap 并不保证键的迭代顺序。当我们需要按照键的自然顺序或自定义规则进行排序遍历时,SortedMap 接口便派上了用场。
SortedMap 是 Map 接口的子接口,它最重要的特性是保证键按照升序排列 。本文将深入剖析 SortedMap 的核心方法、实现类(如 TreeMap)、与 HashMap 的对比,并通过丰富的代码示例帮助你快速上手。
2. SortedMap 接口概述
2.1 什么是 SortedMap
SortedMap 位于 java.util 包中,继承自 Map 接口。它扩展了 Map 的语义,规定其中的键必须按照自然顺序 (如 Integer 从小到大、String 按字典序)或构造时指定的比较器(Comparator) 进行排序。
java
public interface SortedMap<K, V> extends Map<K, V> {
// 核心方法
Comparator<? super K> comparator();
SortedMap<K, V> subMap(K fromKey, K toKey);
SortedMap<K, V> headMap(K toKey);
SortedMap<K, V> tailMap(K fromKey);
K firstKey();
K lastKey();
}
2.2 与 Map 的关系
SortedMap 在 Map 的基础上增加了对顺序 的约束。所有实现 SortedMap 的类(最典型的是 TreeMap)在遍历时,键都是有序的。
3. SortedMap 的核心方法
3.1 获取首尾键:firstKey() 与 lastKey()
这两个方法分别返回当前映射中的最小键和最大键。如果映射为空,会抛出 NoSuchElementException。
java
import java.util.SortedMap;
import java.util.TreeMap;
public class FirstLastKeyExample {
public static void main(String[] args) {
SortedMap<Integer, String> scores = new TreeMap<>();
scores.put(85, "张三");
scores.put(92, "李四");
scores.put(76, "王五");
System.out.println("最小键: " + scores.firstKey()); // 76
System.out.println("最大键: " + scores.lastKey()); // 92
}
}
3.2 视图截取:subMap()、headMap()、tailMap()
这三个方法是 SortedMap 最强大的功能,用于获取原映射的子视图:
subMap(fromKey, toKey):返回键从fromKey(包含)到toKey(不包含)的部分。headMap(toKey):返回键严格小于toKey的部分。tailMap(fromKey):返回键大于等于fromKey的部分。
java
import java.util.SortedMap;
import java.util.TreeMap;
public class SubMapExample {
public static void main(String[] args) {
SortedMap<Integer, String> map = new TreeMap<>();
map.put(1, "一");
map.put(2, "二");
map.put(3, "三");
map.put(4, "四");
map.put(5, "五");
SortedMap<Integer, String> sub = map.subMap(2, 5);
System.out.println("subMap(2,5): " + sub); // {2=二, 3=三, 4=四}
SortedMap<Integer, String> head = map.headMap(3);
System.out.println("headMap(3): " + head); // {1=一, 2=二}
SortedMap<Integer, String> tail = map.tailMap(3);
System.out.println("tailMap(3): " + tail); // {3=三, 4=四, 5=五}
}
}
注意 :这三个方法返回的是视图(View),而非副本。对子视图的修改会直接反映到原映射中。
3.3 获取比较器:comparator()
返回当前映射使用的比较器。如果使用键的自然顺序,则返回 null。
java
import java.util.Comparator;
import java.util.SortedMap;
import java.util.TreeMap;
public class ComparatorExample {
public static void main(String[] args) {
// 使用自然顺序
SortedMap<Integer, String> natural = new TreeMap<>();
System.out.println("自然顺序 comparator: " + natural.comparator()); // null
// 使用自定义比较器(降序)
SortedMap<Integer, String> custom = new TreeMap<>(Comparator.reverseOrder());
custom.put(1, "一");
custom.put(2, "二");
System.out.println("自定义 comparator: " + custom.comparator());
System.out.println("降序结果: " + custom); // {2=二, 1=一}
}
}
4. SortedMap 的主要实现类:TreeMap
4.1 TreeMap 简介
TreeMap 是 SortedMap 最常用的实现类,底层基于红黑树(Red-Black Tree) 数据结构。它保证了 containsKey、get、put、remove 等操作的时间复杂度为 O(log n)。
java
import java.util.SortedMap;
import java.util.TreeMap;
public class TreeMapExample {
public static void main(String[] args) {
SortedMap<String, Integer> fruitPrices = new TreeMap<>();
fruitPrices.put("香蕉", 3);
fruitPrices.put("苹果", 5);
fruitPrices.put("橙子", 4);
// 按键的自然顺序(字典序)遍历
for (String key : fruitPrices.keySet()) {
System.out.println(key + " -> " + fruitPrices.get(key));
}
// 输出顺序:橙子、苹果、香蕉(按拼音/字典序)
}
}
4.2 自定义排序规则
当键的自然顺序不符合需求时,可以在构造 TreeMap 时传入 Comparator。
java
import java.util.Comparator;
import java.util.SortedMap;
import java.util.TreeMap;
public class CustomSortExample {
public static void main(String[] args) {
// 按字符串长度排序
SortedMap<String, String> map = new TreeMap<>(Comparator.comparingInt(String::length));
map.put("Java", "语言");
map.put("Python", "语言");
map.put("C", "语言");
System.out.println(map.keySet()); // [C, Java, Python](按长度升序)
}
}
5. SortedMap 与 HashMap 的对比
| 特性 | SortedMap(TreeMap) | HashMap |
|---|---|---|
| 键的顺序 | 有序(自然顺序或 Comparator) | 无序 |
| 时间复杂度 | O(log n) | O(1)(平均) |
| 是否允许 null 键 | 不允许(TreeMap 会抛 NPE) | 允许(仅一个) |
| 底层结构 | 红黑树 | 哈希表 + 链表/红黑树 |
| 适用场景 | 需要排序遍历、范围查询 | 快速存取、不关心顺序 |
java
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class CompareExample {
public static void main(String[] args) {
Map<Integer, String> hashMap = new HashMap<>();
hashMap.put(3, "三");
hashMap.put(1, "一");
hashMap.put(2, "二");
System.out.println("HashMap 遍历顺序: " + hashMap.keySet()); // 无序
Map<Integer, String> treeMap = new TreeMap<>(hashMap);
System.out.println("TreeMap 遍历顺序: " + treeMap.keySet()); // [1, 2, 3]
}
}
6. 实战案例:学生成绩排名
下面通过一个完整的案例,演示如何使用 SortedMap 实现学生成绩的自动排名。
java
import java.util.SortedMap;
import java.util.TreeMap;
public class ScoreRanking {
public static void main(String[] args) {
// 键为分数,值为学生姓名(分数相同则后者覆盖前者,实际可用 List 处理)
SortedMap<Integer, String> ranking = new TreeMap<>();
ranking.put(88, "张三");
ranking.put(95, "李四");
ranking.put(72, "王五");
ranking.put(91, "赵六");
System.out.println("=== 成绩从低到高排名 ===");
for (var entry : ranking.entrySet()) {
System.out.println(entry.getKey() + " 分 -> " + entry.getValue());
}
System.out.println("\n=== 前三名(成绩最高的 3 人)===");
// 降序排列后取前 3
SortedMap<Integer, String> desc = new TreeMap<>(java.util.Collections.reverseOrder());
desc.putAll(ranking);
int count = 0;
for (var entry : desc.entrySet()) {
if (count++ >= 3) break;
System.out.println(entry.getKey() + " 分 -> " + entry.getValue());
}
}
}
7. 使用注意事项
7.1 null 键问题
TreeMap 不允许 null 键,因为无法对 null 进行排序比较。如果尝试插入 null 键,会抛出 NullPointerException。
java
SortedMap<Integer, String> map = new TreeMap<>();
map.put(null, "value"); // 抛出 NullPointerException
7.2 键必须可比较
如果使用自然顺序,键必须实现 Comparable 接口;否则必须在构造时提供 Comparator,否则运行时抛出 ClassCastException。
7.3 子视图的边界
subMap 的 fromKey 必须小于 toKey,否则抛出 IllegalArgumentException。
8. 总结
SortedMap 是 Java 集合框架中处理有序键值对 的核心接口。通过 TreeMap 实现,我们可以轻松实现键的自动排序、范围查询和有序遍历。在实际开发中,当业务需要按顺序展示数据(如排行榜、时间线、字典序列表)时,SortedMap 是比 HashMap 更合适的选择。
掌握 SortedMap 的三大视图方法(subMap、headMap、tailMap)和自定义比较器,将帮助你在处理复杂排序需求时游刃有余。