TreeMap 是 Java 集合框架中基于红黑树实现的有序 Map 集合,它实现了 NavigableMap 接口(继承自 SortedMap),能够按照键的自然顺序或自定义比较器对键值对进行排序。与 HashMap 相比,TreeMap 牺牲了部分插入和查询性能,换取了有序性和丰富的导航能力。
有序性:
默认按照键的自然顺序(实现 Comparable 接口的 compareTo 方法)排序。
可通过构造方法指定自定义比较器(Comparator),灵活定义排序规则。
所有键必须具有可比性(要么实现 Comparable,要么提供 Comparator),否则会抛出 ClassCastException。
存储结构:
底层采用红黑树(一种自平衡二叉查找树)实现,确保键的有序性和操作效率。
红黑树的特性保证了插入、删除、查询操作的时间复杂度为 O (log n)。
键值约束:
键不能为 null(会抛出 NullPointerException)。
值可以为 null,且支持多个键对应 null 值。
非线程安全:
本身不是线程安全的,多线程环境下需手动同步(如使用 Collections.synchronizedSortedMap())。
导航能力:
提供丰富的导航方法(如获取最小 / 大键、范围查询等),这是其区别于其他 Map 的核心优势。
TreeMap 提供了 4 种构造方法:
TreeMap():创建一个空 TreeMap,使用键的自然顺序排序(要求键实现 Comparable 接口)。
TreeMap(Comparator<? super K> comparator):创建一个空 TreeMap,使用指定的比较器排序。
TreeMap(Map<? extends K, ? extends V> m):从指定 Map 复制键值对,使用键的自然顺序排序。
TreeMap(SortedMap<K, ? extends V> m):从指定 SortedMap 复制键值对,沿用其排序规则。
TreeMap 是基于红黑树实现的有序映射,其核心特性是键的自然排序(或自定义比较器排序),所有方法均围绕有序性和红黑树的高效操作展开。以下是其主要方法的详细介绍:
功能:向映射中添加键值对,若键已存在则覆盖旧值。
有序性核心:
插入时会根据键的排序规则(默认 Comparable 接口,或构造时指定的 Comparator)将键值对插入红黑树的对应位置,保证树始终有序。
若键为 null:
若使用默认排序(键实现 Comparable),插入 null 会抛出 NullPointerException(因 null 无法调用 compareTo 方法)。
若自定义比较器且支持 null 键,则可正常插入(需在比较器中处理 null 逻辑)。
返回值:返回键之前关联的值(若键不存在则返回 null)。
功能:根据键获取对应的值,利用红黑树的二分查找特性,时间复杂度为 O(log n)。
查找逻辑:
通过排序规则(compareTo 或 Comparator)在红黑树中定位键的位置。
若找到匹配的键,返回关联的值;若键不存在或不支持 null 键时传入 null,返回 null。
注意:查找时的 “键匹配” 依赖排序规则,而非 equals 方法(但建议 compareTo 结果与 equals 逻辑一致,避免逻辑矛盾)。
功能:删除指定键对应的键值对,删除后会自动调整红黑树结构,维持有序性和平衡性。
返回值:返回被删除键关联的值(若键不存在,返回 null)。
特殊情况:若键为 null 且不支持 null 键,直接返回 null(不抛出异常)。
功能:判断映射中是否包含指定键,内部逻辑与 get 方法一致(通过排序规则查找键的位置)。
返回值:找到键则返回 true,否则返回 false(包括键为 null 且不支持的情况)。
功能:返回映射中键值对的总数量,内部维护一个计数器,插入 / 删除时实时更新,时间复杂度为 O(1)。
注意:返回值为当前有效键值对数量,红黑树的结构调整不影响此计数。
功能:清空映射中所有键值对,同时重置红黑树(将根节点置为 null),计数器归零。
行为:清空后 size() 返回 0,isEmpty() 返回 true,后续插入会重新构建红黑树。
功能:返回映射中最小的键(按排序规则定义的 “最小”)。
实现逻辑:红黑树的最小键对应树的最左子节点,通过循环遍历左子节点直至 null 找到。
异常:若映射为空,调用此方法会抛出 NoSuchElementException。
功能:返回映射中最大的键(按排序规则定义的 “最大”)。
实现逻辑:红黑树的最大键对应树的最右子节点,通过循环遍历右子节点直至 null 找到。
异常:若映射为空,调用此方法会抛出 NoSuchElementException。
这类方法用于获取键在指定范围内的子映射,返回的 SortedMap 是原映射的视图(修改子映射会同步影响原映射,反之亦然)。
功能:返回键严格小于 toKey 的所有键值对构成的子映射。
注意:
子映射的键范围是 [最小键, toKey)(左闭右开)。
若 toKey 小于原映射的最小键,返回空的子映射;若 toKey 大于等于最大键,返回原映射的完整视图。
功能:返回键大于等于 fromKey 的所有键值对构成的子映射。
注意:子映射的键范围是 [fromKey, 最大键](左闭右闭)。
功能:返回键在 [fromKey, toKey) 范围内(左闭右开)的所有键值对构成的子映射。
异常:若 fromKey 大于 toKey,或 fromKey/toKey 超出原映射的键范围(且比较器不支持),会抛出 IllegalArgumentException。
这类方法用于查找与指定键 “最接近” 的键值对(Entry),返回结果包含键和值,适用于需要 “近似匹配” 的场景。
功能:返回键严格小于 key 的所有键中,最大键对应的键值对(即 “小于 key 的最大 Entry”)。
返回值:找到则返回该 Entry,若不存在(如 key 小于所有键)则返回 null。
功能:返回键严格大于 key 的所有键中,最小键对应的键值对(即 “大于 key 的最小 Entry”)。
返回值:找到则返回该 Entry,若不存在(如 key 大于所有键)则返回 null。
功能:返回键小于或等于 key 的所有键中,最大键对应的键值对(即 “不大于 key 的最大 Entry”)。
区别于 lowerEntry:若 key 本身存在于映射中,直接返回 key 对应的 Entry;lowerEntry 则返回小于 key 的最大 Entry。
功能:返回键大于或等于 key 的所有键中,最小键对应的键值对(即 “不小于 key 的最小 Entry”)。
区别于 higherEntry:若 key 本身存在于映射中,直接返回 key 对应的 Entry;higherEntry 则返回大于 key 的最小 Entry。
功能:删除并返回映射中最小键对应的键值对(等同于先调用 firstKey() 找到最小键,再调用 remove() 删除)。
返回值:删除的 Entry,若映射为空则返回 null(不抛出异常,区别于 firstKey())。
功能:删除并返回映射中最大键对应的键值对(等同于先调用 lastKey() 找到最大键,再调用 remove() 删除)。
返回值:删除的 Entry,若映射为空则返回 null。
功能:返回键的逆序集合视图(即按原排序规则的相反顺序排列),返回的 NavigableSet 支持反向迭代、反向范围查询等操作。
行为:该集合是原映射的视图,修改原映射会同步更新此集合;迭代时按 “从大到小” 的顺序遍历键。
示例:若原映射键按 1,2,3 排序,descendingKeySet() 遍历结果为 3,2,1。
下面将演示,当 TreeMap 未指定自定义比较器(Comparator)时,TreeMap 会依赖键(key)自身实现的 Comparable 接口来确定排序规则。是 TreeMap 最常用的排序模式之一。例如:
package com.hxstrive.java_collection.treeMap;
import java.util.TreeMap;
import java.util.Map;
public class TreeMapExample {
public static void main(String[] args) {
// 键为 Integer(已实现 Comparable),默认按自然顺序(升序)排序
TreeMap<Integer, String> numMap = new TreeMap<>();
numMap.put(3, "Three");
numMap.put(1, "One");
numMap.put(2, "Two");
numMap.put(5, "Five");
numMap.put(4, "Four");
// 迭代顺序为键的升序
System.out.println("自然顺序(升序):");
for (Map.Entry<Integer, String> entry : numMap.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
// 导航方法示例
System.out.println("\n最小键:" + numMap.firstKey()); // 1
System.out.println("最大键:" + numMap.lastKey()); // 5
System.out.println("小于3的最大键值对:" + numMap.lowerEntry(3)); // 2=Two
}
}运行结果:
自然顺序(升序): 1=One 2=Two 3=Three 4=Four 5=Five 最小键:1 最大键:5 小于3的最大键值对:2=Two Process finished with exit code 0
TreeMap 还支持通过自定义 Comparator 实现灵活的排序规则,包括降序排序。当 TreeMap 构造时传入自定义比较器,会优先使用该比较器替代键的自然排序(Comparable 接口),从而实现键的降序排列。例如:
package com.hxstrive.java_collection.treeMap;
import java.util.TreeMap;
import java.util.Comparator;
public class TreeMapComparatorExample {
public static void main(String[] args) {
// 自定义比较器:按字符串长度降序排序
TreeMap<String, Integer> strMap = new TreeMap<>(
Comparator.comparingInt(String::length).reversed()
);
strMap.put("apple", 5);
strMap.put("banana", 6);
strMap.put("pear", 4);
strMap.put("orange", 6); // 长度与banana相同,按自然顺序排列
System.out.println("按字符串长度降序:");
System.out.println(strMap);
strMap.forEach((k, v) -> System.out.println(k + "(长度:" + v + ")"));
}
}运行结果:
按字符串长度降序:
{banana=6, apple=5, pear=4}
banana(长度:6)
apple(长度:5)
pear(长度:4)观察输出结果,你会发现“orange”没有被放入到 Map 中,这是为什么呢?
TreeMap 判定两个键是否 “相等” 的依据是比较器的返回值:若 compare(k1, k2) == 0,则认为 k1 和 k2 是同一个键,新键值对会覆盖旧键值对(键不会重复添加)。
当执行 strMap.put("orange", 6) 时:
TreeMap 发现 orange 与已存在的 banana 在比较器中返回 0 → 判定为同一键。
此时会用新值(6)覆盖旧值(6,值相同不影响),但键仍然是 banana(因为先放入的键会被保留,后放入的同键会覆盖值但不改变键)。
因此,最终 TreeMap 中只会保留 banana 这个键,orange 作为 “重复键” 被忽略(仅覆盖值,键未新增)。简单修改一下代码进行验证:
package com.hxstrive.java_collection.treeMap;
import java.util.TreeMap;
import java.util.Comparator;
public class TreeMapComparatorExample {
public static void main(String[] args) {
// 自定义比较器:按字符串长度降序排序
TreeMap<String, Integer> strMap = new TreeMap<>(
Comparator.comparingInt(String::length).reversed()
);
strMap.put("apple", 5);
strMap.put("banana", 6);
strMap.put("pear", 4);
strMap.put("orange", 16); // 长度与banana相同,按自然顺序排列
System.out.println("按字符串长度降序:");
System.out.println(strMap);
strMap.forEach((k, v) -> System.out.println(k + "(长度:" + v + ")"));
}
}运行结果:
按字符串长度降序:
{banana=16, apple=5, pear=4}
banana(长度:16)
apple(长度:5)
pear(长度:4)此时输出结果中,banana 只为 16 了,值被覆盖了。
TreeMap 是 Java 集合框架中具有有序性的 Map 实现,与其他常见 Map(如 HashMap、LinkedHashMap、Hashtable、ConcurrentHashMap 等)相比,在数据结构、排序特性、性能等方面有显著差异。如下表:
特性 | TreeMap | HashMap | LinkedHashMap |
有序性 | 自然顺序或自定义顺序 | 无序 | 插入顺序或访问顺序 |
底层结构 | 红黑树 | 哈希表 | 哈希表 + 双向链表 |
查找效率 | O(log n) | 平均 O (1) | 平均 O (1) |
插入 / 删除效率 | O(log n) | 平均 O (1) | 平均 O (1)(额外链表操作) |
键是否允许为 null | 不允许 | 允许(1 个) | 允许(1 个) |
导航能力 | 丰富(范围查询等) | 无 | 无 |
下面是 TreeMap 适用场景:
(1)需要有序键值对的场景:如排行榜(按分数排序)、字典(按字母顺序排序)等。
(2)范围查询场景:需频繁执行「获取小于 / 大于某个键的元素」「获取区间内元素」等操作时。
(3)自定义排序规则场景:键的自然顺序不符合需求,需通过比较器灵活定义排序逻辑时。
注意,TreeMap 是有序 Map 的首选实现,尤其适合需要频繁进行范围查询和排序操作的场景,虽然性能略低于 HashMap,但提供了更强大的有序性和导航能力。
更多信息参考 https://docs.oracle.com/javase/8/docs/api/java/util/TreeMap.html API 文档。