TreeMap 实现原理

张开发
2026/4/19 2:23:45 15 分钟阅读

分享文章

TreeMap 实现原理
在Java的集合框架中HashMap以其O(1)的高效查找性能占据了统治地位但在需要“有序”数据的场景下HashMap便显得力不从心。此时TreeMap作为SortedMap和NavigableMap接口的核心实现凭借其基于红黑树的底层结构成为了处理有序键值对的首选。一、 TreeMap的定义与核心特性TreeMap是一个基于红黑树Red-Black Tree实现的NavigableMap。它的核心特性可以概括为以下几点有序性TreeMap会根据键Key的自然顺序Natural Ordering或者指定的Comparator比较器对键值对进行排序。高效性由于底层采用红黑树TreeMap保证了put、get、remove操作的时间复杂度均为O(log n)。非线程安全与HashMap一样TreeMap不是线程安全的。如果在多线程环境下使用需要通过Collections.synchronizedSortedMap进行包装或使用ConcurrentSkipListMap。Null键限制TreeMap不允许null键除非使用了特定的Comparator允许因为null无法参与比较排序会抛出NullPointerException。二、 底层原理红黑树Red-Black Tree要理解TreeMap必须先理解红黑树。红黑树是一种自平衡的二叉查找树BST。它通过在每个节点增加一个存储位来表示节点的颜色红色或黑色并通过一系列规则确保树在操作过程中保持“近似平衡”。1. 红黑树的五条铁律TreeMap的底层节点Entry严格遵循以下五条规则颜色规则每个节点要么是红色要么是黑色。根节点规则根节点永远是黑色。叶子节点规则所有叶子节点NIL节点即空节点都是黑色。红色节点规则如果一个节点是红色那么它的两个子节点都必须是黑色即不能有两个连续的红色节点。黑色平衡规则从任意节点到其每个叶子节点的所有路径都包含相同数量的黑色节点。2. 为什么选择红黑树相比于AVL树严格平衡二叉树红黑树的平衡要求相对宽松。AVL树追求极致的平衡查询效率极高但插入和删除时需要大量的旋转操作来维护平衡。红黑树牺牲了一部分查询效率树高略高换取了插入和删除时的更低开销旋转次数更少。在TreeMap这种需要频繁插入、删除且需要保持有序的场景下红黑树是一个性能极佳的折中方案。三、 排序机制自然排序与定制排序TreeMap之所以能“有序”是因为它在插入节点时通过比较键的大小来确定节点在树中的位置。它支持两种排序方式1. 自然排序当TreeMap的键实现了Comparable接口如Integer、String等时TreeMap会默认使用键的自然顺序进行排序。核心逻辑调用key.compareTo(otherKey)。2. 定制排序如果在创建TreeMap实例时传入了一个Comparator对象TreeMap将优先使用该比较器来决定键的顺序。核心逻辑调用comparator.compare(key, otherKey)。注意如果键没有实现Comparable接口且没有提供Comparator在插入元素时会抛出ClassCastException。四、 源码深度剖析让我们深入JDK源码以JDK 8为例看看TreeMap是如何运作的。1. 核心内部类EntryTreeMap的节点定义如下它比HashMap的节点多了一个parent引用和color属性这是为了维护树的结构。static final class EntryK,V implements Map.EntryK,V { K key; V value; EntryK,V left; // 左孩子 EntryK,V right; // 右孩子 EntryK,V parent; // 父节点用于回溯 boolean color BLACK; // 颜色默认黑色 // 构造函数等省略... }2. 核心属性// 比较器如果为null则表示使用自然排序 private final Comparator? super K comparator; // 红黑树的根节点 private transient EntryK,V root; // 树的节点数量 private transient int size 0;3. put方法流程解析TreeMap的put操作主要分为三个步骤查找插入位置从根节点开始利用比较器comparator或Comparable将待插入的key与当前节点的key进行比较。如果key小往左子树走如果key大往右子树走。如果找到相同的key则更新value并返回旧值。如果走到null说明找到了插入位置。插入新节点创建新的Entry节点。关键点新插入的节点默认颜色为红色。这是为了尽量减少对“黑色平衡规则”的破坏从而减少平衡调整的复杂度。修复红黑树fixAfterInsertion插入红色节点可能会破坏“红色节点规则”即出现父子节点同为红色。TreeMap会通过变色和旋转左旋、右旋来恢复红黑树的性质。public V put(K key, V value) { EntryK,V t root; if (t null) { // 1. 如果是空树直接作为根节点需校验key不为null root new Entry(key, value, null); size 1; return null; } int cmp; EntryK,V parent; Comparator? super K cpr comparator; // 2. 寻找插入位置 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; else return t.setValue(value); // Key已存在更新值 } while (t ! null); } else { // 使用自然排序需强转Comparable // ...省略部分代码逻辑同上 } // 3. 插入新节点红色 EntryK,V e new Entry(key, value, parent); if (cmp 0) parent.left e; else parent.right e; // 4. 修复红黑树平衡 fixAfterInsertion(e); size; modCount; return null; }4. 平衡调整策略fixAfterInsertion当插入一个红色节点x时如果其父节点p也是红色就会发生冲突。修复逻辑主要分三种情况假设p是祖父节点g的左孩子叔叔节点是红色将父节点p和叔叔节点u变为黑色祖父节点g变为红色然后将x指针上移到g继续向上检查。叔叔节点是黑色且x是右孩子先对p进行左旋转化为情况3。叔叔节点是黑色且x是左孩子将p变为黑色g变为红色然后对g进行右旋。删除操作remove同样复杂涉及查找、删除如果是红色直接删黑色需要调整和修复fixAfterDeletion核心思想也是通过旋转和变色来维持黑高平衡。五、 TreeMap vs HashMap特性HashMapTreeMap底层结构数组 链表/红黑树红黑树排序无序有序自然顺序或Comparator时间复杂度O(1)O(log n)Null键允许一个null键不允许自然排序下适用场景快速查找、缓存、通用Map范围查询、排行榜、有序报表六、 实战应用场景范围查询TreeMap实现了NavigableMap提供了强大的范围操作方法subMap(fromKey, toKey)返回指定范围内的子Map。headMap(toKey)返回小于指定键的所有元素。tailMap(fromKey)返回大于指定键的所有元素。查找最邻近值ceilingKey(K key)返回大于等于key的最小键。floorKey(K key)返回小于等于key的最大键。higherKey(K key)返回严格大于key的最小键。lowerKey(K key)返回严格小于key的最大键。排行榜系统利用TreeMap的自动排序特性可以轻松实现按分数排序的排行榜Key为分数Value为用户ID。七、 面试高频题目TreeMap和HashMap的区别是什么回答要点数据结构红黑树vs哈希表、有序性、性能O(log n) vs O(1)、Null键支持。TreeMap的底层数据结构是什么为什么选择它回答要点红黑树。因为它在插入、删除和查找之间取得了平衡虽然查找比AVL树稍慢但插入删除效率更高适合频繁变动的数据集。TreeMap允许null键吗为什么回答要点通常不允许。因为在插入时需要调用compareTo或compare方法如果key为null会导致NullPointerException。除非自定义Comparator专门处理了null值。如何实现TreeMap的倒序遍历回答要点可以使用descendingMap()方法获取一个逆序的视图或者在构造函数中传入Comparator.reverseOrder()。红黑树的插入和删除是如何保持平衡的回答要点通过变色Color Flip和旋转Rotate Left/Right。插入时新节点默认为红若父红则调整删除时若删黑节点则需复杂的“双黑”修复逻辑。

更多文章