【Java源码阅读系列13】深度解读Java TreeMap 源码

TreeMap 是 Java 集合框架中基于红黑树(Red-Black Tree)实现的有序映射(NavigableMap),它支持键的自然排序或自定义排序,且所有核心操作(putgetremove)的时间复杂度为 O(log n)。本文将从数据结构、核心方法、设计模式应用等角度,深入解析 TreeMap 的源码实现。

一、核心数据结构:红黑树

TreeMap 的核心是一棵红黑树,这是一种自平衡的二叉搜索树(BST)。红黑树通过颜色标记(红/黑)和 5 条约束保证树的平衡,避免退化为链表(时间复杂度退化为 O(n))。红黑树的约束如下:

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点是黑色。
  3. 所有叶子节点(NIL)是黑色。
  4. 红色节点的两个子节点都是黑色(不存在连续红节点)。
  5. 从任一节点到其每个叶子的所有路径包含相同数量的黑色节点(黑高平衡)。

TreeMap 的内部节点由 Entry 类表示,包含键(key)、值(value)、左右子节点(left/right)、父节点(parent)和颜色标记(color)。

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;

        /**
         * Make a new cell with given key, value, and parent, and with
         * {@code null} child links, and BLACK color.
         */
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }
 }

二、构造函数与排序策略

TreeMap 支持两种排序方式,通过构造函数初始化排序规则:

  • 自然排序:无参构造函数(TreeMap()),要求键实现 Comparable 接口。
  • 自定义排序:带 Comparator 的构造函数(TreeMap(Comparator<? super K> comparator)),使用指定比较器排序。
// 自然排序构造函数(源码第 118 行)
public TreeMap() {
    comparator = null;
}

// 自定义排序构造函数(源码第 126 行)
public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}

构造函数的核心是初始化 comparator 字段。后续插入、查找操作会根据 comparator 是否为 null,选择使用 Comparable.compareToComparator.compare 进行键的比较。


三、核心方法解析

1. put(K key, V value):插入与平衡

put 方法的核心逻辑:在红黑树中找到插入位置,插入新节点后通过旋转和颜色调整修复红黑树约束。

public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {
        compare(key, key); // type (and possibly null) check

        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    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);
        } while (t != null);
    }
    else {
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}

步骤分解

  • 空树处理:若树为空(root == null),直接创建根节点(颜色默认为黑色,因红黑树约束 2)。
  • 查找插入位置:根据 comparator 是否存在,选择比较方式(Comparator.compareComparable.compareTo),从根节点开始向下查找,直到找到合适的叶子节点位置。
  • 插入新节点:新节点颜色初始化为红色(避免破坏黑高约束),并链接到父节点的左或右子节点。
  • 修复红黑树:调用 fixAfterInsertion(Entry<K,V> x) 修复可能违反的红黑树约束(如连续红节点)。
    private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED;

        while (x != null && x != root && x.parent.color == RED) {
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }

fixAfterInsertion 修复逻辑: 插入新节点(红色)可能导致父节点为红色(违反约束 4)。修复分两种情况(以父节点是祖父节点的左子节点为例):

  • 叔叔节点为红色:将父节点和叔叔节点设为黑色,祖父节点设为红色,然后向上递归修复祖父节点(可能引发更高层的冲突)。
  • 叔叔节点为黑色:若新节点是父节点的右子节点,先左旋父节点,转换为左子节点情况;然后将父节点设为黑色,祖父节点设为红色,右旋祖父节点。

通过上述操作,最终保证根节点为黑色(约束 2),并恢复红黑树的平衡。

2. get(Object key):查找节点

public V get(Object key) {
    Entry<K,V> p = getEntry(key);
    return (p==null ? null : p.value);
}
final Entry<K,V> getEntry(Object key) {
    // Offload comparator-based version for sake of performance
    if (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;
        else
            return p;
    }
    return null;
}

get 方法通过 getEntry(key) 实现(源码第 373-400 行),核心是二叉搜索树的查找逻辑:

  • comparator 存在,使用 getEntryUsingComparator 按比较器查找。
  • 否则,要求键实现 Comparable,使用 compareTo 方法比较。
  • 从根节点开始,根据比较结果向左或右子树递归查找,直到找到相等的键或遍历至叶子节点(返回 null)。

3. remove(Object key):删除与平衡

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

remove 方法首先调用 getEntry 找到待删除节点,然后调用 deleteEntry 执行删除并修复红黑树。

private void deleteEntry(Entry<K,V> p) {
    modCount++;
    size--;
    // If strictly internal, copy successor's element to p and then
    // point to successor.
    if (p.left != null && p.right != null) {
        Entry<K,V> s = successor(p);
        p.key = s.key;
        p.value = s.value;
        p = s;
    } // p has 2 children
    // Start fixup at replacement node, if it exists.
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);
    if (replacement != null) {
        // Link replacement to parent
        replacement.parent = p.parent;
        if (p.parent == null)
            root = replacement;
        else if (p == p.parent.left)
            p.parent.left  = replacement;
        else
            p.parent.right = replacement;
        // Null out links so they are OK to use by fixAfterDeletion
        p.left = p.right = p.parent = null;
        // Fix replacement
        if (p.color == BLACK)
            fixAfterDeletion(replacement);
    } else if (p.parent == null) { // return if we are the only nod
        root = null;
    } else { //  No children. Use self as phantom replacement and u
        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;
        }
    }
}

删除逻辑

  • 节点有两个子节点:找到后继节点(右子树的最小节点),将后继节点的键值复制到当前节点,转换为删除后继节点(最多一个子节点)。
  • 节点有一个子节点或无子节点:用子节点(或 null)替换当前节点的位置,若删除的是黑色节点,调用 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 { // symmetric
            Entry<K,V> sib = leftOf(parentOf(x));
            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateRight(parentOf(x));
                sib = leftOf(parentOf(x));
            }
            if (colorOf(rightOf(sib)) == BLACK &&
                colorOf(leftOf(sib)) == BLACK) {
                setColor(sib, RED);
                x = parentOf(x);
            } else {
                if (colorOf(leftOf(sib)) == BLACK) {
                    setColor(rightOf(sib), BLACK);
                    setColor(sib, RED);
                    rotateLeft(sib);
                    sib = leftOf(parentOf(x));
                }
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(leftOf(sib), BLACK);
                rotateRight(parentOf(x));
                x = root;
            }
        }
    }
    setColor(x, BLACK);
}

fixAfterDeletion 修复逻辑: 删除黑色节点会导致其所在路径的黑高减少(违反约束 5)。修复通过调整兄弟节点的颜色和旋转,确保路径黑高一致。例如,若删除节点是左子节点:

  • 若兄弟节点为红色,左旋父节点并调整颜色,转换为兄弟节点为黑色的情况。
  • 若兄弟节点的两个子节点均为黑色,将兄弟节点设为红色,向上递归修复父节点。
  • 否则,通过旋转和颜色调整,将额外的黑色转移到树的高层。

四、导航功能与子映射

TreeMap 实现了 NavigableMap 接口,提供丰富的导航方法(如 lowerEntryceilingKey 等),用于查找接近指定键的节点。这些方法通过 getLowerEntrygetCeilingEntry 等内部方法实现,核心是二叉搜索树的边界查找。

final Entry<K,V> getCeilingEntry(K key) {
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = compare(key, p.key);
        if (cmp < 0) {
            if (p.left != null)
                p = p.left;
            else
                return p;
        } else if (cmp > 0) {
            if (p.right != null) {
                p = p.right;
            } else {
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                while (parent != null && ch == parent.right) {
                    ch = parent;
                    parent = parent.parent;
                }
                return parent;
            }
        } else
            return p;
    }
    return null;
}

例如,getCeilingEntry(K key)(源码第 433-458 行)查找大于等于 key 的最小键节点:

  • 从根节点开始比较,若 key 小于当前节点,向左子树查找;若大于,向右子树查找。
  • 若右子树为空,向上回溯父节点,直到找到第一个祖先节点大于 key

五、设计模式应用

1. 迭代器模式(Iterator Pattern)

TreeMap 的内部迭代器(如 EntryIteratorKeyIterator)通过 PrivateEntryIterator 基类实现,统一了遍历逻辑。子类只需重写 next() 方法定义具体返回值(键、值或键值对),符合迭代器模式“提供统一遍历接口”的思想。

// 迭代器基类(源码第 1533 行)
abstract class PrivateEntryIterator<T> implements Iterator<T> {
    Entry<K,V> next;
    Entry<K,V> lastReturned;
    int expectedModCount;

    public final boolean hasNext() { return next != null; }
    final Entry<K,V> nextEntry() { /* 遍历逻辑 */ }
}

// 键迭代器(源码第 1589 行)
final class KeyIterator extends PrivateEntryIterator<K> {
    public K next() { return nextEntry().key; }
}

2. 模板方法模式(Template Method Pattern)

NavigableSubMap 抽象类定义了子映射的通用操作(如 absLowestabsHighest),具体实现由子类 AscendingSubMapDescendingSubMap 完成。例如,subLowest() 在升序子映射中返回最小节点,在降序子映射中返回最大节点,体现了模板方法模式“定义骨架,子类实现细节”的特点。

3. 工厂方法模式(Factory Method Pattern)

subMapheadMaptailMap 等方法根据排序方向(升序/降序)返回 AscendingSubMapDescendingSubMap 实例,隐藏了具体子类的创建逻辑,符合工厂方法模式“由子类决定实例化哪个类”的特征。


六、线程安全性与 fail-fast 机制

TreeMap 是非线程安全的。为保证多线程下的正确性,需通过 Collections.synchronizedSortedMap 包装。其迭代器(如 EntryIterator)实现了 fail-fast 机制:通过 modCount 字段记录结构修改次数,若迭代过程中 modCount 变化,抛出 ConcurrentModificationException,快速检测并发修改错误。


七、总结与适用场景

TreeMap 是基于红黑树的有序映射,核心优势是有序性和对数时间复杂度操作,适用于需要按键排序的场景(如统计、排序查询)。但需注意:

  • 插入/删除操作因涉及红黑树修复,性能略低于 HashMap(但仍为 O(log n))。
  • 非线程安全,多线程环境需额外同步。
  • 键需支持比较(自然排序或自定义 Comparator)。

通过深入理解 TreeMap 的源码,我们不仅掌握了红黑树的实际应用,还学习了设计模式在集合框架中的巧妙运用,为优化代码设计和解决复杂问题提供了思路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值