JDK 8 TreeMap 详解及详细源码展示

JDK 8 中的 TreeMap 是基于红黑树(Red-Black Tree)实现的 有序映射表,它继承自 AbstractMap 并实现了 NavigableMap 接口(NavigableMap 继承自 SortedMap)。TreeMap 的核心特性是按键的自然顺序或自定义比较器排序,并支持高效的导航操作(如查找最接近的键、范围查询等)。

一、TreeMap 的核心特性

特性说明
有序性键(Key)按自然顺序(若键实现 Comparable)或自定义 Comparator 排序。
时间复杂度插入、删除、查找等操作的平均/最坏时间复杂度为 O(log n)(红黑树的高度为 log n)。
不允许重复键键唯一(若插入相同键,新值覆盖旧值)。
不允许 null 键若使用自然排序(无 Comparator),键必须非 null(因 Comparable 不允许 null);若使用自定义 Comparator,可能允许 null(但需 Comparator 处理 null)。
导航方法支持 ceilingKeyfloorKeyhigherKeylowerKey 等方法,快速查找最接近的键。

二、TreeMap 的核心结构

1. 继承与接口
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable
  • NavigableMap:扩展了 SortedMap,增加了导航方法(如查找最近键)和视图操作(如子映射)。
  • AbstractMap:提供 Map 接口的骨架实现,减少重复代码。
2. 核心属性
// 红黑树的根节点
private transient Entry<K,V> root;

// 元素数量
private transient int size = 0;

// 修改次数(用于快速失败迭代器)
private transient int modCount = 0;

// 比较器(若为 null,使用键的自然排序)
private final Comparator<? super K> comparator;

// 序列化版本号
private static final long serialVersionUID = 9172710721974048732L;
3. 红黑树节点(Entry)

TreeMap 的内部类 Entry 继承了 HashMap.Node(JDK 8 中 HashMap 的节点类),但扩展了红黑树所需的属性:

static final class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> left;    // 左子节点
    Entry<K,V> right;   // 右子节点
    Entry<K,V> parent;  // 父节点
    boolean color = BLACK; // 节点颜色(默认黑色)

    // 构造方法(与 HashMap.Node 兼容)
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}
  • 红黑树节点颜色color 字段标记节点颜色(REDBLACK),默认新节点为红色(避免破坏黑高)。
  • 父/子节点指针:通过 parentleftright 指针连接,形成树结构。

三、构造方法

TreeMap 提供了多种构造方法,核心是初始化比较器和根节点:

1. 默认构造方法(自然排序)
public TreeMap() {
    comparator = null; // 使用键的自然排序
}
2. 指定比较器的构造方法
public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator; // 使用自定义比较器
}
3. 从 Map 初始化(按键排序)
public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    putAll(m); // 插入所有键值对,按键排序
}
4. 从 SortedMap 初始化(保留排序)
public TreeMap(SortedMap<K, ? extends V> m) {
    comparator = m.comparator(); // 继承原 SortedMap 的比较器
    try {
        buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
    } catch (java.io.IOException cannotHappen) {
    } catch (ClassNotFoundException cannotHappen) {
    }
}
  • buildFromSorted 是优化方法,用于从已排序的集合高效构建红黑树(类似归并排序的线性时间构建)。

四、核心方法解析

1. put(K key, V value):插入键值对

put 方法的核心逻辑是:找到键的插入位置,插入新节点,然后调整红黑树以保持平衡

步骤 1:检查键是否合法

若使用自然排序(comparator == null),键必须非 null(否则抛出 NullPointerException);若使用自定义比较器,comparator.compare(key, key) 不能返回 0(否则视为重复键?实际取决于比较器实现)。

步骤 2:找到插入位置

通过 compare 方法比较键的大小,从根节点开始遍历,找到合适的父节点:

private Entry<K,V> putTreeVal(int h, K k, V v, boolean animate) {
    Entry<K,V> parent; // 父节点
    Entry<K,V> p;      // 当前遍历节点
    int cmp;           // 比较结果
    Entry<K,V> t = root;

    // 根节点为空,直接创建新节点作为根
    if (t == null) {
        root = new Entry<>(h, k, v, null);
        size = 1;
        modCount++;
        return null;
    }

    // 遍历树,找到插入位置
    while (t != null) {
        parent = t;
        cmp = compare(k, t.key); // 比较当前键与节点键
        if (cmp < 0) {
            t = t.left; // 键更小,向左子树
        } else if (cmp > 0) {
            t = t.right; // 键更大,向右子树
        } else {
            // 键已存在,替换值并返回旧值
            V oldValue = t.value;
            t.value = v;
            return oldValue;
        }
    }

    // 找到父节点,创建新节点
    Entry<K,V> e = new Entry<>(h, k, v, parent);
    if (cmp < 0) {
        parent.left = e;
    } else {
        parent.right = e;
    }

    // 插入后调整红黑树
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}
步骤 3:调整红黑树(fixAfterInsertion)

插入新节点后,可能破坏红黑树的平衡性质(如连续两个红色节点)。fixAfterInsertion 通过变色旋转修复平衡:

  • 红黑树性质

    1. 每个节点是红色或黑色。
    2. 根节点是黑色。
    3. 所有叶子节点(NIL)是黑色。
    4. 红色节点的两个子节点都是黑色(无连续红色)。
    5. 从任一节点到其叶子的所有路径包含相同数量的黑色节点(黑高相同)。
  • 插入修复逻辑
    新插入的节点默认是红色(避免破坏黑高)。若父节点是黑色,无需调整;若父节点是红色,则违反性质4,需根据叔叔节点的颜色调整:

    • 叔叔节点是红色:将父、叔节点设为黑色,祖父节点设为红色,向上递归检查祖父节点。
    • 叔叔节点是黑色(或 NIL:通过旋转(左旋/右旋)和变色修复。
private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED; // 新节点默认红色

    // 循环直到父节点是黑色或到达根节点
    while (x != null && 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是父的右子节点(需左旋)
                    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; // 根节点始终黑色
}
2. get(Object key):查找值

get 方法通过比较键的大小,在红黑树中快速定位目标节点:

public V get(Object key) {
    Entry<K,V> p = getEntry(key);
    return (p == null ? null : p.value);
}

final Entry<K,V> getEntry(Object key) {
    // 根据比较器或自然排序查找
    if (comparator != null) {
        return getUsingComparator(key);
    } else {
        if (key == null) {
            throw new NullPointerException();
        }
        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;
    }
}
3. remove(Object key):删除键值对

remove 方法的核心是:找到要删除的节点,用后继节点替换它,然后调整红黑树

步骤 1:找到待删除节点

通过 getEntry 找到目标节点 p,若不存在则返回。

步骤 2:确定替换节点
  • p 无左子节点,用右子节点替换;
  • p 无右子节点,用左子节点替换;
  • p 有左右子节点,用**右子树的最小节点(后继)**替换 p
步骤 3:调整红黑树

删除节点后,可能破坏红黑树性质(如黑高变化),需通过 fixAfterDeletion 调整。

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

    V oldValue = p.value;
    deleteEntry(p); // 删除节点并调整树
    return oldValue;
}

private void deleteEntry(Entry<K,V> p) {
    modCount++;
    size--;

    // 确定替换节点(successor)
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);

    // 情况 1:替换节点存在(有左或右子节点)
    if (replacement != null) {
        replacement.parent = p.parent; // 替换节点的父指向 p 的父
        if (p.parent == null) {
            root = replacement; // p 是根节点,替换为根
        } else if (p == p.parent.left) {
            p.parent.left = replacement; // 替换为左子节点
        } else {
            p.parent.right = replacement; // 替换为右子节点
        }

        // 若 p 是被删除的节点(非替换节点),复制其值到 p(后续调整用)
        p.left = p.right = p.parent = null;
        if (p.color == BLACK) {
            fixAfterDeletion(replacement); // 调整红黑树
        }
    } 
    // 情况 2:替换节点不存在(p 是叶子节点)
    else if (p.parent == null) {
        root = null; // 树为空
    } 
    // 情况 3:p 是根节点且有子节点(但子节点无后代)
    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;
        }
    }
}
步骤 4:调整红黑树(fixAfterDeletion)

删除黑色节点会破坏黑高性质(路径黑高减少),需通过变色和旋转修复:

private void fixAfterDeletion(Entry<K,V> x) {
    while (x != root && colorOf(x) == BLACK) {
        if (x == leftOf(parentOf(x))) { // x 是左子节点
            Entry<K,V> s = rightOf(parentOf(x)); // 兄弟节点
            if (colorOf(s) == RED) { // 兄弟是红色
                setColor(s, BLACK); // 兄变黑
                setColor(parentOf(x), RED); // 父变红
                rotateLeft(parentOf(x)); // 左旋父节点
                s = rightOf(parentOf(x)); // 更新兄弟节点
            }
            if (colorOf(leftOf(s)) == BLACK && colorOf(rightOf(s)) == BLACK) { // 兄弟的子节点都是黑色
                setColor(s, RED); // 兄变红
                x = parentOf(x); // 继续向上检查
            } else {
                if (colorOf(rightOf(s)) == BLACK) { // 兄的右子节点是黑色
                    setColor(leftOf(s), BLACK); // 兄的左子节点变黑
                    setColor(s, RED); // 兄变红
                    rotateRight(s); // 右旋兄节点
                    s = rightOf(parentOf(x)); // 更新兄弟节点
                }
                setColor(s, colorOf(parentOf(x))); // 兄继承父的颜色
                setColor(parentOf(x), BLACK); // 父变黑
                setColor(rightOf(s), BLACK); // 兄的右子节点变黑
                rotateLeft(parentOf(x)); // 左旋父节点
                x = root; // 退出循环
            }
        } else { // x 是右子节点(对称逻辑)
            Entry<K,V> s = leftOf(parentOf(x));
            if (colorOf(s) == RED) {
                setColor(s, BLACK);
                setColor(parentOf(x), RED);
                rotateRight(parentOf(x));
                s = leftOf(parentOf(x));
            }
            if (colorOf(rightOf(s)) == BLACK && colorOf(leftOf(s)) == BLACK) {
                setColor(s, RED);
                x = parentOf(x);
            } else {
                if (colorOf(leftOf(s)) == BLACK) {
                    setColor(rightOf(s), BLACK);
                    setColor(s, RED);
                    rotateLeft(s);
                    s = leftOf(parentOf(x));
                }
                setColor(s, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(leftOf(s), BLACK);
                rotateRight(parentOf(x));
                x = root;
            }
        }
    }
    setColor(x, BLACK); // 最终 x 设为黑色(可能是根节点)
}

五、导航方法(NavigableMap 接口)

TreeMap 实现了 NavigableMap,提供了以下关键方法:

1. ceilingKey(K key):查找大于等于 key 的最小键
public K ceilingKey(K key) {
    return keyOrNull(ceilingEntry(key));
}

final Entry<K,V> ceilingEntry(K key) {
    return getCeilingEntry(key); // 内部实现
}
2. floorKey(K key):查找小于等于 key 的最大键
public K floorKey(K key) {
    return keyOrNull(floorEntry(key));
}
3. higherKey(K key):查找大于 key 的最小键
public K higherKey(K key) {
    return keyOrNull(higherEntry(key));
}
4. lowerKey(K key):查找小于 key 的最大键
public K lowerKey(K key) {
    return keyOrNull(lowerEntry(key));
}
5. subMap(K fromKey, K toKey):返回键在 [fromKey, toKey) 的子映射
public NavigableMap<K,V> subMap(K fromKey, K toKey) {
    return new SubMap<>(this, fromKey, true, toKey, false);
}
  • 子映射通过 SubMap 内部类实现,共享原树的节点,通过视图机制实现高效操作。

六、关键源码细节

1. 红黑树的旋转操作

红黑树通过左旋右旋调整结构,保持平衡:

// 左旋节点 x(x 的右子节点 y 成为新的父节点)
private void rotateLeft(Entry<K,V> x) {
    Entry<K,V> y = x.right;
    x.right = y.left; // y 的左子树成为 x 的右子树
    if (y.left != null) {
        y.left.parent = x; // 更新 y 左子节点的父
    }
    y.parent = x.parent; // y 的父指向 x 的父
    if (x.parent == null) {
        root = y; // x 是根,更新根为 y
    } else if (x == x.parent.left) {
        x.parent.left = y; // x 是左子节点,更新父的左子节点为 y
    } else {
        x.parent.right = y; // x 是右子节点,更新父的右子节点为 y
    }
    y.left = x; // x 成为 y 的左子节点
    x.parent = y; // x 的父指向 y
}

// 右旋节点 y(y 的左子节点 x 成为新的父节点)
private void rotateRight(Entry<K,V> y) {
    Entry<K,V> x = y.left;
    y.left = x.right; // x 的右子树成为 y 的左子树
    if (x.right != null) {
        x.right.parent = y; // 更新 x 右子节点的父
    }
    x.parent = y.parent; // x 的父指向 y 的父
    if (y.parent == null) {
        root = x; // y 是根,更新根为 x
    } else if (y == y.parent.right) {
        y.parent.right = x; // y 是右子节点,更新父的右子节点为 x
    } else {
        y.parent.left = x; // y 是左子节点,更新父的左子节点为 x
    }
    x.right = y; // y 成为 x 的右子节点
    y.parent = x; // y 的父指向 x
}
2. 比较逻辑

TreeMap 使用 compare 方法比较键的大小,优先使用自定义比较器,否则使用键的自然排序:

private int compare(Object k1, Object k2) {
    return (comparator != null) ? comparator.compare(k1, k2) : ((Comparable<? super K>)k1).compareTo(k2);
}

七、总结

TreeMap 是基于红黑树的有序映射,核心优势是按键排序高效的导航操作。其内部通过红黑树维护平衡,保证了插入、删除、查找的 O(log n) 时间复杂度。实际开发中,若需要有序性和导航功能,TreeMap 是首选;若只需快速查找且不要求顺序,HashMap 更高效(平均 O(1) 时间复杂度)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值