Java基础 Map集合框架 TreeMap
TreeMap
- TreeMap类架构与继承关系
- TreeMap核心特性
- 数据结构:红黑树
- 有序性:基于BST的中序遍历
- 非线程安全
- 键约束:键不能为null
- 底层实现原理
- 底层数据结构:红黑树
- 排序比较规则(TreeMap的四种构造方法)
- TreeMap()- 自然顺序排序
- TreeMap(Comparator<? super K> comparator) - 自定义比较器
- TreeMap(Map<? extends K,? extends V> m) - 从现有Map创建
- TreeMap(SortedMap<K,? extends V> m) - 从有序Map创建
- 有序性:基于中序遍历的实现机制
- 有序迭代器TreeMap.KeyIterator的实现
- 中序后继节点算法(核心)
- 核心操作逻辑
- put插入
- remove插入
- 关键API
- 特有导航方法(核心优势)
- 高级特性:自定义排序
- 逆序排列示例
- 复合排序示例(先按年龄降序,再按姓名升序)
- 与HashMap对比
- 经典应用场景
- 注意事项
集合框架中的
TreeMap
。作为
Map
接口的一个重要实现,
TreeMap
基于红黑树(Red-Black tree)数据结构,提供了键的
有序
存储
TreeMap类架构与继承关系
public class TreeMap<K,V>extends AbstractMap<K,V>implements NavigableMap<K,V>, Cloneable, java.io.Serializable
1.继承抽象类AbstractMap
和,表明拥有Map集合的基础通用实现方法
2.实现了 NavigableMap
接口:NavigableMap接口是Java集合框架的一部分,它扩展了SortedMap
接口,并提供了一些额外的导航方法
NavigableMap接口的主要特点如下:
1.导航方法:NavigableMap接口提供了一系列的导航方法,如lowerKey、floorKey、ceilingKey、higherKey等,这些方法可以帮助我们找到最接近某个搜索目标的键。例如,higherKey(K key)方法会返回大于指定键的最小键,而floorKey(K key)方法则会返回小于或等于指定键的最大键14。
2.移除并返回元素:NavigableMap接口还提供了一些方法来移除并返回集合中的元素。例如,pollFirstEntry()方法会移除并返回集合中的第一个元素(按照键的顺序),而pollLastEntry()方法则会移除并返回最后一个元素14。
3.子映射:NavigableMap接口提供了子映射的方法,如subMap(K fromKey, K toKey)、headMap(K toKey)和tailMap(K fromKey),这些方法可以返回一个新的映射,其中包含原映射中键位于指定范围内的所有元素14。
而TreeMap是NavigableMap接口的一个具体实现类,它通过红黑树实现,保证了元素的有序状态,并提供了NavigableMap接口中定义的所有导航方法
TreeMap核心特性
数据结构:红黑树
// 最小逻辑单元键值对 作为树节点
static final class Entry<K,V> implements Map.Entry<K,V> {K key;V value;Entry<K,V> left; // 左子节点Entry<K,V> right; // 右子节点Entry<K,V> parent; // 父节点boolean color = BLACK; // 节点颜色
}
//构造方法里面没有 左右子节点,说明构建新节点时 左右子节点为null 这是隐藏的覆盖关系
Entry(K key, V value, Entry<K,V> parent) {this.key = key;this.value = value;this.parent = parent;}
红黑树本质:自平衡二叉搜索树,TreeMap区别与HashMap多种数据结构组合使用,仅使用红黑树结构
有序性:基于BST的中序遍历
有序性的保证
1.红黑树的数据结构:自平衡
二叉搜索树,二叉搜索树BST
保证了树中的每个节点,其左子树的所有节点的键都小于该节点的键,而右子树的所有节点的键都大于该节点的键,节点按照键的顺序进行存储,红黑树自平衡意味这新增或者删除节点后会自调整二叉搜索树的平衡
2.强制排序规则:TreeMap中的元素可以按照键的自然排序
(如果键实现了Comparable接口)进行对比key值找到合适的插入节点位置,或者根据创建TreeMap时提供的定制排序Comparator
进行比对。如果两种方式都没有用,则在插入时会抛出ClassCastException
3.中序遍历:对应二叉搜索树中序遍历结果是正序
,TreeMap的遍历方式确实是基于中序遍历的
有序性的体现
1.遍历:当使用迭代器遍历TreeMap的entry、key或者value集合时,会按照键的升序进行遍历
Map<Integer,String> treeMap = new TreeMap<>();treeMap.put(3, "three");treeMap.put(1, "one");treeMap.put(2, "two");treeMap.forEach((K,V)->System.out.println(K));// 1 2 3
2.视图:TreeMap提供了若干有序视图
- keySet():返回一个有序的键集合
- entrySet():返回一个有序的键值对集合
- values():返回一个值的集合,注意值的顺序与键的顺序一致Set<Integer> set = treeMap.keySet();set.forEach(s ->System.out.println(s));// 123Set<Map.Entry<Integer,String>> setMap = treeMap.entrySet();for ( Map.Entry<Integer, String> entry : setMap) {System.out.println("Key: " + entry.getKey());//1 2 3 }Collection<String> collection = treeMap.values();collection.forEach(v->System.out.println(v));//one two three
3.首尾元素:TreeMap提供了获取第一个(最小)和最后一个(最大)键的方法
- firstKey():返回最小的键
- lastKey():返回最大的键
((TreeMap<Integer, String>) treeMap).firstKey();// 1
((TreeMap<Integer, String>) treeMap).lastKey(); // 3
4.范围视图:TreeMap提供了返回子映射的方法,这些子映射也是有序的
- headMap(K toKey):返回键小于toKey的部分映射
- tailMap(K fromKey):返回键大于等于fromKey的部分映射
- subMap(K fromKey, K toKey):返回键在[fromKey, toKey)范围内的子映射
((TreeMap<Integer, String>) treeMap).headMap(3);//{1=one, 2=two}
((TreeMap<Integer, String>) treeMap).tailMap(1);// {1=one, 2=two, 3=three}
((TreeMap<Integer, String>) treeMap).subMap(1,3);// {1=one, 2=two}
5.逆序:TreeMap有一个方法descendingMap()
,返回一个逆序
的映射视图,即按照键的降序
排列
NavigableMap<Integer, String> descending = ((TreeMap<Integer, String>) treeMap).descendingMap();for (Integer key : descending.keySet()) {System.out.println(key); // 输出:3, 2, 1}
非线程安全
类似HashMap,需外部同步或使用Collections.synchronizedSortedMap
包装
键约束:键不能为null
键不能为null
(若使用自然排序,因为null
无法比较会报错)
值允许为null
底层实现原理
底层数据结构:红黑树
红黑树BBT是自平衡二叉搜索树,二叉搜索树BST保证任意节点都比左侧子节点大 右侧子节点小,这种特殊的数据结构保证了查找时候的时间复制度O(log n),而红黑树自平衡BST,在新增或删除节点后会进行调整维持BST特性
红黑树规则
1.每个节点要么是红色,要么是黑色
2.根节点是黑色的
3.每个叶子节点(NIL节点,空节点)是黑色的
4.如果一个节点是红色的,则它的两个子节点都是黑色的
5.从任一节点到其每个叶子的所有路径包含相同数目的黑色节点
排序比较规则(TreeMap的四种构造方法)
TreeMap的四种构造方法带有对应的比较规则
TreeMap()- 自然顺序排序
TreeMap()
: 使用键的自然顺序
进行排序。要求所有键必须实现Comparable
接口,否则在插入时会抛出ClassCastException
public TreeMap() {comparator = null; // 标记使用自然排序
}
核心机制
1.将内部比较器 comparator 设为 null 作为特殊标记
2.首次插入元素时,通过类型检查强制键实现 Comparable:// put()方法中的检查逻辑if (key == null) throw new NullPointerException();Comparable<? super K> k = (Comparable<? super K>) key;
3.排序规则:使用键的 compareTo() 方法
4.应用场景:键为 Integer、String 等 JDK 内置可比类型自定义类实现 Comparable 接口
TreeMap<>使用示例
TreeMap<Integer, String> map = new TreeMap<>();map.put(3, "三"); map.put(1, "一"); // 自动排序:1->3
TreeMap(Comparator<? super K> comparator) - 自定义比较器
TreeMap(Comparator<? super K> comparator)
: 使用自定义的比较器进行排序。如果提供了比较器,则根据比较器的规则排序,而不要求键实现Comparable
接口
public TreeMap(Comparator<? super K> comparator) {this.comparator = comparator; // 存储自定义比较器
}
核心机制
1.优先级高于自然排序(即使键实现 Comparable 也无效)在新增节点put方法中 会优先选择定制排序 如果定制排序存在就直接使用了// split comparator and comparable pathsComparator<? super K> cpr = comparator;if (cpr != null) {} else{}2.插入时使用比较器的 compare() 方法:// put()方法中的分支cmp = cpr.compare(key, t.key); // 使用传入的比较器
3.特殊能力:支持非 Comparable 类型键可定义与自然顺序不同的排序(如降序)允许处理 null 键(需在比较器中显式处理)
TreeMap(Comparator<? super K> comparator) 使用示例
// 创建按字符串长度排序的TreeMapTreeMap<String, Integer> map = new TreeMap<>(Comparator.comparingInt(String::length));map.put("apple", 1); map.put("pear", 2); // 排序:pear(4)->apple(5)
TreeMap(Map<? extends K,? extends V> m) - 从现有Map创建
TreeMap(Map<? extends K,? extends V> m)
: 从现有的Map创建TreeMap,使用自然顺序排序。它首先创建一个空的TreeMap(自然顺序),然后将传入的Map中的所有元素放入。注意,传入的Map不一定有序,但TreeMap会根据自然顺序重新排序
核心机制
1.典型陷阱:强制自然排序:即使源Map是 LinkedHashMap等有序集合,也丢弃其顺序Map<String, Integer> linkedMap = new LinkedHashMap<>();linkedMap.put("z", 1);linkedMap.put("a", 2); // 插入顺序:z->a// treeMap 输出顺序变为 a->z(字母顺序)TreeMap<String, Integer> treeMap = new TreeMap<>(linkedMap);2.插入优化:a.检测源 Map 是否为 SortedMap:// putAll()中的优化if (m instanceof SortedMap) {// 使用buildFromSorted优化构建}b.普通 `Map` 则遍历调用put()(时间复杂度 O(n log n))
TreeMap(Map<? extends K,? extends V> m)使用示例
// 创建一个 HashMap 并添加一些键值对 (无序的)Map<String, Integer> hashMap = new HashMap<>();hashMap.put("banana", 3);hashMap.put("apple", 1);hashMap.put("cherry", 2);// 使用 HashMap 创建一个 TreeMapTreeMap<String, Integer> treeMap = new TreeMap<>(hashMap);// 输出 TreeMap 的内容System.out.println("TreeMap: " + treeMap);
TreeMap(SortedMap<K,? extends V> m) - 从有序Map创建
4.TreeMap(SortedMap<K,? extends V> m)
: 从现有的有序Map(SortedMap)创建TreeMap
。它会继承原有序Map的比较器(即排序规则)和所有元素。这是最高效的创建方式,因为原Map已经有序,TreeMap可以更高效地构建
public TreeMap(SortedMap<K, ? extends V> m) {comparator = m.comparator(); // 继承原比较器try {buildFromSorted(m.size(), m.entrySet().iterator(), null, null);} catch (Exception cannotHappen) {}
}
核心优势
1.比较器继承:完全保留原 SortedMap 的排序规则
2.高效构建:使用 buildFromSorted 方法时间复杂度 O(n)(优于普通插入的 O(n log n))递归构建完美平衡红黑树:// 核心构建逻辑Entry<K,V> buildFromSorted(int level, int lo, int hi,...){int mid = (lo + hi) >>> 1; // 找中间节点Entry<K,V> left = buildFromSorted(...lo, mid-1); Entry<K,V> right = buildFromSorted(...mid+1, hi);// 连接左右子树}
TreeMap(SortedMap<K,? extends V> m) 使用示例
SortedMap<String, Integer> sortedMap = new TreeMap<>(reverseOrder());
sortedMap.put("orange", 1);
sortedMap.put("apple", 2); // 顺序:orange->apple// 完美继承反向排序规则
TreeMap<String, Integer> copy = new TreeMap<>(sortedMap);
有序性:基于中序遍历的实现机制
节点结构(Entry)
static final class Entry<K,V> implements Map.Entry<K,V> {K key;V value;Entry<K,V> left; // 左子节点Entry<K,V> right; // 右子节点Entry<K,V> parent; // 父节点boolean color = BLACK; // 红黑树颜色
}
有序迭代器TreeMap.KeyIterator的实现
使用中序遍历(左-根-右 顺序)
获取有序序列:对于二叉搜索树,中序遍历的结果是正序结果
final class KeyIterator extends PrivateEntryIterator<K> {KeyIterator(Entry<K,V> first) {super(first);}public K next() {return nextEntry().key; // 按中序顺序返回下一个键}
}abstract class PrivateEntryIterator<E> implements Iterator<E> {Entry<K,V> next;Entry<K,V> lastReturned;PrivateEntryIterator(Entry<K,V> first) {lastReturned = null;next = first; // 从最左侧节点开始}final Entry<K,V> nextEntry() {Entry<K,V> e = next;if (e == null) throw new NoSuchElementException();next = successor(e); // 关键:获取中序后继节点lastReturned = e;return e;}
}
中序后继节点算法(核心)
static <K,V> Entry<K,V> successor(Entry<K,V> t) {if (t == null) return null;// 情况1:存在右子树else if (t.right != null) {Entry<K,V> p = t.right;while (p.left != null) // 找右子树的最左节点p = p.left;return p;}// 情况2:无右子树,向上查找else {Entry<K,V> p = t.parent;Entry<K,V> ch = t;// 找到第一个是左子节点的祖先while (p != null && ch == p.right) {ch = p;p = p.parent;}return p;}
}
逻辑解释
示例二叉搜索树示例如下8/ \4 10/ \ / \2 6 9 14
/ \ / \ / \
1 3 5 7 13 16步骤逻辑
1.先递归找到最左侧节点1 即整个树的最小节点,这是二叉搜查树的性质 记录 1
2.节点1的左右子树为null,上移处理父节点2 记录1 2
3.判断有右子树,递归处理右子树 排序结果 3 记录 123
先左子树 - 根节点 - 右子树 的顺序4.然后2的左右子树处理完了 上移处理父节点4,如果站在4的角度看,刚刚处理的都是它的左子树
现在处理4 记录 1 2 3 4
5.然后判断有右子树,按照上面的逻辑 递归处理 先找已6为根节点的树的左左侧节点 5节点6的左子树处理完毕,然后到6,然后处理6的右子树...
这样层层递归 直到根节点8记录后,开始处理8的右子树(都比8大) 当前已经排序的是 1 2 3 4 5 6 7 8对于右子树而言10是根节点 在10左侧都比它小 在右侧都比它大,从10的最左侧找到最小节点9
然后按照上面的步骤 排序结果 9 10 13 14 16排序结果一起的话 1 2 3 4 5 6 7 8 9 10 13 14 16
核心操作逻辑
put插入
put操作包括查找插入位置、插入新节点、调整红黑树平衡
步骤分解
1.判断根节点是否为空,为空则直接创建根节点
public V put(K key, V value) {// 从根节点开始遍历Entry<K,V> t = root;// 情况1:树为空(首次插入)if (t == null) {compare(key, key); // 类型检查和空指针检查root = new Entry<>(key, value, null); // 创建根节点size = 1;modCount++; // 结构修改计数return null; // 无旧值返回}} //这里既然没有引用比较返回结果,那就单纯检查 key类型和是否为空 final int compare(Object k1, Object k2) {return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2): comparator.compare((K)k1, (K)k2);}
2. 根据是否有比较器(Comparator)分为两个分支:使用比较器或使用键的自然顺序(Comparable)
3. 在循环中查找插入位置:从根节点开始,根据比较结果向左或向右子树移动,直到找到合适的位置(叶节点的子节点为空)
4. 如果遇到相等的键,则替换值并返回旧值
int cmp; //比较结果值Entry<K,V> parent; // 记录当前节点的父节点// 获取比较器cpr (区分自然排序/定制排序)Comparator<? super K> cpr = comparator;// 情况2:如果cpr != null,说明有定制比较器,使用定制比较器if (cpr != null) {do {parent = t; // 记录父节点cmp = cpr.compare(key, t.key); // 使用比较器比较if (cmp < 0)t = t.left; // 向左子树搜索else if (cmp > 0)t = t.right; // 向右子树搜索elsereturn t.setValue(value); // 键已存在,更新值} while (t != null); // 直到叶子节点}// 情况3:使用自然排序else {if (key == null) // 自然排序禁止null键throw new NullPointerException();// 将key强制转换为Comparable类型@SuppressWarnings("unchecked")Comparable<? super K> k = (Comparable<? super K>) key;do {parent = t;cmp = k.compareTo(t.key); // 调用Comparable接口比较if (cmp < 0)t = t.left;else if (cmp > 0)t = t.right;elsereturn t.setValue(value);} while (t != null);}
5. 创建新节点并插入到查找到的位置(作为左子节点或右子节点)
// 创建新节点(此时t=null, parent是叶子节点)// 创建的新节点是默认带两个左右NULL子节点的Entry<K,V> e = new Entry<>(key, value, parent);// 确定新节点位置if (cmp < 0)parent.left = e; // 作为左子节点elseparent.right = e; // 作为右子节点
6. 插入后调整红黑树(fixAfterInsertion)以保持平衡
// 红黑树平衡调整(核心)fixAfterInsertion(e);
7. 更新大小和修改次数
// 更新计数size++;modCount++; // 结构修改计数+1return null; // 无旧值返回
- 插入:
- 按二叉搜索树规则插入新节点(初始为红色)
- 通过旋转(左旋/右旋)和变色维持红黑树规则
remove插入
public V remove(Object key) {// 1.getEntry方法根据key找到节点Entry<K,V> p = getEntry(key);if (p == null)return null;V oldValue = p.value;//2.如果节点存在,则调用deleteEntry删除节点,并返回旧值;否则返回nulldeleteEntry(p);return oldValue;}
getEntry方法
final Entry<K,V> getEntry(Object key) {// Offload comparator-based version for sake of performanceif (comparator != null)return getEntryUsingComparator(key);if (key == null)throw new NullPointerException();@SuppressWarnings("unchecked")Comparable<? super K> k = (Comparable<? super K>) key;Entry<K,V> p = root;// 利用比较器 左右子树对比 循环往下直到找打需要删除的节点while (p != null) {int cmp = k.compareTo(p.key);if (cmp < 0)p = p.left;else if (cmp > 0)p = p.right;elsereturn p;}return null;}
deleteEntry
private void deleteEntry(Entry<K,V> p) {modCount++; // 结构修改计数size--; // 元素数量减少// 处理双子树节点(第3-8行):情况1:被删除节点有两个子节点就需要找到替换节点if (p.left != null && p.right != null) {Entry<K,V> s = successor(p); // 找到后继节点(右子树最小节点)p.key = s.key; // 用后继节点的键替换被删除节点p.value = s.value; // 用后继节点的值替换被删除节点p = s; // 将删除目标转为后继节点(此时p最多有一个子节点)}// 获取替换节点(第11行):情况2:被删除节点有子节点(此时最多只有一个)Entry<K,V> replacement = (p.left != null ? p.left : p.right);//处理有子节点的情况(第13-28行)if (replacement != null) { // 有子节点的情况// 连接父节点和子节点replacement.parent = p.parent;if (p.parent == null)root = replacement;else if (p == p.parent.left)p.parent.left = replacement;elsep.parent.right = replacement;// 清空被删除节点的引用p.left = p.right = p.parent = null;// 如果删除的是黑色节点,需要修复平衡if (p.color == BLACK)fixAfterDeletion(replacement);}// 情况3:被删除节点是根节点且没有子节点else if (p.parent == null) { //处理叶子节点/根节点(第30-45行)root = null;}// 情况4:被删除节点是叶子节点else {// 处理平衡(如果删除节点是黑色节点才需要调整平衡:黑高性)if (p.color == BLACK)fixAfterDeletion(p);// 从父节点移除引用if (p.parent != null) {if (p == p.parent.left)p.parent.left = null;else if (p == p.parent.right)p.parent.right = null;p.parent = null;}}
}
详情操作的步骤逻辑解释在上面的红黑树详情链接里了,这里简单说下处理逻辑
fixAfterDeletion
// x是删除后需要修复的起始点
private void fixAfterDeletion(Entry<K,V> x) {while (x != root && colorOf(x) == BLACK) {if (x == leftOf(parentOf(x))) {// 处理x是左子节点的情况} else { // 处理x是右子节点的情况(对称逻辑)}}//无论循环如何退出,都确保x为黑色setColor(x, BLACK); // 最终确保x为黑色
}
处理x是左子节点的情况(完整逻辑)
Entry<K,V> sib = rightOf(parentOf(x)); // 获取兄弟节点(父节点的右子节点)// Case 1: 兄弟节点为红色
if (colorOf(sib) == RED) {setColor(sib, BLACK); // 1. 兄弟染黑setColor(parentOf(x), RED); // 2. 父节点染红rotateLeft(parentOf(x)); // 3. 左旋父节点sib = rightOf(parentOf(x)); // 4. 更新兄弟节点
}
Case 1 作用:将红色兄弟情况转化为黑色兄弟情况,为后续处理做准备
// Case 2: 兄弟节点的两个子节点都是黑色
if (colorOf(leftOf(sib)) == BLACK &&colorOf(rightOf(sib)) == BLACK) {setColor(sib, RED); // 兄弟染红x = parentOf(x); // 焦点上移到父节点// 不break,继续循环
}
Case 2 关键点:
1.兄弟的子节点全黑时,将兄弟染红
2.焦点x
上移至父节点
3.循环继续:
- 如果父节点是红色:下轮循环条件
colorOf(x)==BLACK
不成立,退出循环 - 如果父节点是黑色:继续处理新的双重黑色节点
else { // 兄弟至少有一个红色子节点// Case 3: 兄弟的右子节点为黑色(左子节点必为红色)if (colorOf(rightOf(sib)) == BLACK) {setColor(leftOf(sib), BLACK); // 兄弟的左子节点染黑setColor(sib, RED); // 兄弟染红rotateRight(sib); // 右旋兄弟节点sib = rightOf(parentOf(x)); // 更新兄弟节点}// Case 4: 兄弟的右子节点为红色setColor(sib, colorOf(parentOf(x))); // 兄弟继承父节点颜色setColor(parentOf(x), BLACK); // 父节点染黑setColor(rightOf(sib), BLACK); // 兄弟的右子节点染黑rotateLeft(parentOf(x)); // 左旋父节点x = root; // 直接跳转到根节点,结束循环
}
Case 3 & 4 处理:
- Case 3:将"远侄子黑色"转换为"远侄子红色"
- 通过染色和旋转调整为Case 4
- Case 4:最终修复
- 重新着色(兄弟继承父色,父和远侄子染黑)
- 左旋父节点
x = root
强制结束循环
对称情况(x是右子节点)和 是左子节点镜像操作处理
关键API
| TreeMap()
| 自然顺序排序 |
| TreeMap(Comparator<? super K> comparator)
| 自定义比较器 |
| TreeMap(Map<? extends K,? extends V> m)
| 从现有Map创建(自然排序)|
| TreeMap(SortedMap<K,? extends V> m)
| 从有序Map创建(继承原顺序)|
特有导航方法(核心优势)
Map.Entry<K,V> firstEntry(); // 获取最小键的条目
K firstKey(); // 获取最小键
K lastKey(); // 获取最大键
Map.Entry<K,V> lastEntry(); // 获取最大键的条目
Map.Entry<K,V> pollFirstEntry(); // 移除并返回最小条目
Map.Entry<K,V> lowerEntry(K key); // 返回严格小于给定键的最大键条目
K floorKey(K key); // 返回小于等于给定键的最大键
SortedMap<K,V> headMap(K toKey); // 返回键小于toKey的部分视图
使用示例
TreeMap<String, Integer> scores = new TreeMap<>();
scores.put("Alice", 90);
scores.put("Bob", 85);
scores.put("Charlie", 95);
// 1. 自然顺序遍历(字典序)
scores.forEach((k,v) -> System.out.println(k + ": " + v));
// 输出:Alice:90, Bob:85, Charlie:95
// 2. 获取第一个条目
System.out.println("First: " + scores.firstEntry()); // Alice=90
// 3. 获取大于"Bob"的最小键
String higher = scores.higherKey("Bob"); // Charlie
// 4. 范围视图([Bob, Charlie))
SortedMap<String, Integer> sub = scores.subMap("Bob", "Charlie");
System.out.println(sub); // {Bob=85}
高级特性:自定义排序
逆序排列示例
// 方法1:使用Collections.reverseOrder()
TreeMap<Integer, String> reverseMap = new TreeMap<>(Collections.reverseOrder());
// 方法2:自定义Comparator
TreeMap<String, Integer> customOrder = new TreeMap<>((s1, s2) -> s2.length() - s1.length() // 按字符串长度降序
);
复合排序示例(先按年龄降序,再按姓名升序)
record Person(String name, int age) {}
Comparator<Person> comp = Comparator.comparingInt(Person::age).reversed().thenComparing(Person::name);
TreeMap<Person, String> employeeMap = new TreeMap<>(comp);
与HashMap对比
特性 | TreeMap | HashMap |
---|---|---|
数据结构 | 红黑树 | 数组+链表/红黑树 |
顺序保证 | 按键有序 | 无序 |
null键 | 不允许(比较异常) | 允许1个 |
性能 | O(log n) | O(1) 平均 |
使用场景 | 需有序访问时 | 高频随机访问 |
我们可以根据实际需求在TreeMap
(有序)和HashMap
(高效)之间做出合理选择。当需要有序遍历或范围查询时,TreeMap
是最佳选择
经典应用场景
1.范围查询
利用subMap()
、headMap()
、tailMap()
高效获取区间数据
// 查询成绩在[80,90]之间的记录NavigableMap<Integer, String> gradeMap = new TreeMap<>();// 填充数据...SortedMap<Integer, String> result = gradeMap.subMap(80, 91);
2.最近邻查找
使用floorEntry()
/ceilingEntry()
查找最接近元素
// 查找最接近3.5的键TreeMap<Double, String> map = new TreeMap<>();map.put(2.0, "A");map.put(3.0, "B");map.put(4.0, "C");Map.Entry<Double, String> lower = map.floorEntry(3.5); // 3.0=BMap.Entry<Double, String> higher = map.ceilingEntry(3.5); // 4.0=C
3.词频统计+排序输出
Map<String, Integer> freqMap = new HashMap<>();// ...统计词频TreeMap<String, Integer> sortedFreq = new TreeMap<>(freqMap);sortedFreq.forEach((word, count) -> System.out.println(word + ": " + count));
注意事项
1.键对象约束
- 若使用自然排序,键类必须实现
Comparable
- 自定义
Comparator
需与equals
逻辑一致(避免违反Map
接口规范)
2.并发访问
// 线程安全包装SortedMap<String, Integer> safeMap = Collections.synchronizedSortedMap(new TreeMap<>());
3.性能调优:
- 在已知数据量时,构造函数中指定初始容量(内部会调整到最近的2的幂)
- 复杂键对象应实现高效的
compareTo()
方法