TreeMap、TreeSet 为何能自动排序?红黑树原理全解析

很多 Java 开发者知道:TreeMap 和 TreeSet 能实现自动排序,但背后的机制究竟是什么?红黑树是如何支撑这一能力的?今天带你从底层结构、核心逻辑、源码实现全面解构。


一、为何 TreeMap / TreeSet 能自动排序?

原因在于它们底层采用了 红黑树(Red-Black Tree),一种具有自平衡性质的二叉搜索树。每次插入或删除元素时,红黑树会自动旋转或染色,以维持排序和查找性能。

  • TreeMap<K, V> 按 Key 排序;

  • TreeSet<E> 基于 TreeMap<E, Object> 实现;

  • 插入元素时会自动维护顺序;

  • 支持自定义排序器:Comparator。


二、红黑树简述:五大性质

红黑树是一种特殊的二叉查找树,额外维护以下五条性质以保持近似平衡:

  • 每个节点要么是红色要么是黑色;

  • 根节点必须是黑色;

  • 所有叶子节点(NIL)是黑色;

  • 红色节点的子节点只能是黑色(不能连续出现两个红色节点);

  • 任意节点到其所有后代叶子节点的路径上,黑色节点数量相同。

这使得树的最长路径不会超过最短路径的两倍,从而保证了查找操作的时间复杂度为 O(log n)


三、TreeMap 插入排序的核心逻辑

当你执行如下代码时:

TreeMap<Integer, String> map = new TreeMap<>();
map.put(3, "C");
map.put(1, "A");
map.put(2, "B");

底层流程:

  • 会先比较 key 的大小(通过 compareTo() 或 Comparator);

  • 插入到红黑树相应的位置;

  • 自动执行必要的旋转或染色,保持平衡;

  • 最终在中序遍历下,key 为有序输出。


四、源码片段:红黑树插入核心逻辑

TreeMap 中 put() 实际调用的是 putVal(),插入完成后会调用 balanceInsertion() 维护红黑树平衡:

private void balanceInsertion(TreeNode<K,V> x) {
    x.red = true;
    while (x != null && x != root && x.parent.red) {
        if (x.parent == x.parent.parent.left) {
            TreeNode<K,V> y = x.parent.parent.right;
            // case 1: 叔叔是红色 -> 染色 + 向上递归
            if (y != null && y.red) { ... }
            // case 2/3: 叔叔是黑色 -> 旋转 + 染色
            else {
                if (x == x.parent.right) {
                    x = x.parent;
                    rotateLeft(x);
                }
                x.parent.red = false;
                x.parent.parent.red = true;
                rotateRight(x.parent.parent);
            }
        } else { ... } // 镜像对称操作
    }
    root.red = false;
}

红黑树插入时总是将新节点标红,然后根据父节点、叔叔节点颜色判断如何旋转/染色。


五、TreeSet 底层结构:其实就是 TreeMap

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable {
    private transient NavigableMap<E,Object> m;
    private static final Object PRESENT = new Object();
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }
}

TreeSet 的每个元素被存储为 TreeMap 的 key,value 用一个哑元对象 PRESENT 代替,因此完全复用了 TreeMap 的自动排序和红黑树能力。


六、支持自定义排序逻辑

默认排序依赖元素自身实现的 Comparable 接口(自然顺序):

TreeMap<String, Integer> map = new TreeMap<>();

如果需要自定义排序规则,可传入 Comparator:

TreeMap<String, Integer> map = new TreeMap<>(
    (a, b) -> b.compareTo(a) // 倒序排序
);

注意:Comparator 会覆盖元素本身的 compareTo() 实现。


七、常见误区与实践建议

保持 Key 的一致性:一旦插入,Key 的排序逻辑(compareTo 或 Comparator)就不能改变,否则查找/删除会失败。

不要使用不具备比较性的 Key 类型:否则将抛出 ClassCastException。

避免 null key:TreeMap 不允许 null key,但允许 null value;

红黑树插入不是稳定排序,不要期望插入顺序和遍历顺序一致。


八、TreeMap 的删除操作及红黑树重平衡

相比插入操作,删除节点对红黑树的结构破坏更大,因此恢复平衡的逻辑更复杂。

在 TreeMap.remove() 中,核心调用路径如下:

public V remove(Object key) {
    Entry<K,V> p = getEntry(key);
    if (p == null)
        return null;
    deleteEntry(p);
    return p.value;
}

在 deleteEntry() 中,如果删除的是红色节点,直接移除即可;但如果是黑色节点,就可能违反红黑树的黑色高度平衡规则,需要调用 fixAfterDeletion() 修复结构。

fixAfterDeletion()的核心处理逻辑包括:

  • 节点兄弟是红色 ➜ 染色+旋转;

  • 节点兄弟是黑色,但子节点有红色 ➜ 单/双旋转;

  • 节点兄弟及其子节点全是黑 ➜ 染色+向上递归修复。

private void fixAfterDeletion(Entry<K,V> x) {
    while (x != root && colorOf(x) == BLACK) {
        if (x == leftOf(parentOf(x))) {
            Entry<K,V> sib = rightOf(parentOf(x));
            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateLeft(parentOf(x));
                sib = rightOf(parentOf(x));
            }
            if (colorOf(leftOf(sib)) == BLACK &&
                colorOf(rightOf(sib)) == BLACK) {
                setColor(sib, RED);
                x = parentOf(x);
            } else {
                if (colorOf(rightOf(sib)) == BLACK) {
                    setColor(leftOf(sib), BLACK);
                    setColor(sib, RED);
                    rotateRight(sib);
                    sib = rightOf(parentOf(x));
                }
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(rightOf(sib), BLACK);
                rotateLeft(parentOf(x));
                x = root;
            }
        } else {
            // 对称处理
        }
    }
    setColor(x, BLACK);
}

九、红黑树旋转图解

左旋操作(rotateLeft):

红黑树转换.drawio.png

右旋操作(rotateRight):

红黑树(右旋).drawio.png

插入/删除过程中的这些操作,正是保证红黑树高度稳定在 log(n) 的关键。


十、TreeMap 与 HashMap 的对比选型

特性

TreeMap

HashMap

底层结构

红黑树(有序)

数组 + 链表 / 红黑树(无序)

插入性能

O(log n)

O(1) ~ O(log n)(高并发退化)

查询性能

O(log n)

O(1) ~ O(log n)

Null Key 支持

❌ 不支持

✅ 支持一个 null key

排序能力

✅ 支持(自然排序 / Comparator)

❌ 无顺序

线程安全

❌ 非线程安全

❌ 非线程安全

选型建议

  • 若需顺序访问,且 key 可比较,选择 TreeMap;

  • 若更关注性能和插入/查询效率,优先选择 HashMap;

  • 并发场景使用 ConcurrentHashMap 或手动加锁。


十一、NavigableMap 高阶用法

TreeMap 实现了 NavigableMap 接口,因此支持许多“导航式”操作,常用于区间查找或范围定位。

TreeMap<Integer, String> map = new TreeMap<>();
map.put(1, "one");
map.put(3, "three");
map.put(5, "five");
map.lowerKey(4);   // 3
map.floorKey(5);   // 5
map.ceilingKey(2); // 3
map.higherKey(5);  // null
map.subMap(2, 5); // key ∈ [2, 5)

这些操作背后的逻辑仍是基于红黑树结构进行定向查找,性能稳定在 O(log n)。


十二、结语

红黑树赋予了 TreeMap / TreeSet 强大的排序与范围查找能力,其复杂但高效的插入、删除和查找机制,是 Java 集合框架中不可或缺的核心之一。

了解其实现原理,有助于你:

  • 做出合理的集合选型;

  • 理解复杂度背后的本质;

  • 写出更高性能、更可控的业务代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小健学 Java

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值