TreeMap 源码分析 - litter-fish/ReadSource GitHub Wiki

类继承关系

1dab9319fc58584cfae0192e62b29811.png

Tree节点

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;
}

总有的属性

// 比较器
private final Comparator<? super K> comparator;
// 树的根节点
private transient Entry<K,V> root;

// 键值对的数量
private transient int size = 0;

// 修改次数,用于快速失败
private transient int modCount = 0;

构造方法

// comparator用键的顺序做比较
public TreeMap() {
    comparator = null;
}

// 构造方法,提供比较器,用指定比较器排序
public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}

// 将Map元素插入TreeMap中,按照键顺序比较
public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    putAll(m);
}

// 构造方法,指定的参数为SortedMap
// 采用m的比较器排序
public TreeMap(SortedMap<K, ? extends V> m) {
    comparator = m.comparator();
    try {
        buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
    } catch (java.io.IOException cannotHappen) {
    } catch (ClassNotFoundException cannotHappen) {
    }
}

红黑树的插入原理及TreeMap插入操作put原理

红黑树性质:

  1. 节点的颜色只能是红色和黑色
  2. 根节点一定是黑色
  3. 所有叶子节点的子节点都是空节点且都是黑色的
  4. 不存在两个连续的红色节点
  5. 任何节点到子树上的黑色节点数量相同

插入情况: 情形1:如果插入的节点既是根节点,此时将颜色设置成黑色即可

情形2:如果插入节点的父节点是黑色的,插入的话并不会改变红黑树的性质

情形3:如果插入节点的父节点P和叔父节点U都为红色

743fc2c288c132fd900e8079af9deb1d.jpeg

处理:交换祖父节点和父亲、叔父节点的颜色,此时祖父节点颜色为红色,

863c62bfc5a1f8fb8f1f23e0fcf6f9a3.jpeg

情形4:如果插入节点的父节点P是红色,而叔父节点U是黑色,且新增节点N是P的右孩子,P是G的左孩子

7b8aeae73f36d811d73fad2ce9c642c7.jpeg

处理:父节点进行一次左旋操作,转换为情形5进行处理

5e556db1cdb80f91efb497c324fce8b7.jpeg

情形5:如果插入节点的父节点P是红色,而叔父节点U是黑色或缺失,同时新增节点N是P的左孩子且P也是G的左孩子

6a37f6553425058c7793f973b6c3e4df.jpeg 处理:

  1. 交换父节点和祖父节点颜色
  2. 将祖父节点做右旋操作

cb7795bb7b7e7adc4bd7bcaf8daf7385.jpeg

插入源码

public V put(K key, V value) {
    Entry<K,V> t = root; // 树的根节点
    // 如果树是一颗空树
    if (t == null) {
        compare(key, key); // type (and possibly null) check
        // 将key指定为树的根
        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    // 获取比较器
    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); // 如果插入key值已经存在,直接替换
        } while (t != null); // 遍历树找到正确的插入位置,红黑树是一个二叉搜索树,so,比根小的值往左走,比根大的往右走
    }
    // 没有指定比较器,使用自然排序方式
    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;
}

红黑树的调整

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)));

            // 情景3:父节点P和叔父节点U都为红色,
            // 需要重新绘制P、U为黑色,祖父G为红色,但是G节点可能是根节点也可能其父节点也是红色,因此需要按照情形1递归处理
            // 如果叔父节点是红色的,父节点也是红色的,且插入节点也是红色的
            if (colorOf(y) == RED) {

                setColor(parentOf(x), BLACK); // 设置父节点为黑色
                setColor(y, BLACK); // 设置叔父节点为黑色
                setColor(parentOf(parentOf(x)), RED); // 将祖父节点设置为红色

                // 祖父节点是红色的,此时将祖父节点这颗子树当做一个整体进行循环处理。
                // 此时如果祖父节点既是根节点则,不满足循环条件,跳出
                // 如果不是根节点则将其当做一个红色节点整体进行循环处理。
                x = parentOf(parentOf(x));
            } else { // 如果叔父节点是黑色的,此时父节点红色,叔父节点红色
                // 情形4.1:如果父节点P是红色,而叔父节点U是黑色,且新增节点N是P的右孩子,P是G的左孩子,此种情况,需要将节点P进行一次左旋操作,然后按照情形5.1进行操作
                if (x == rightOf(parentOf(x))) {
                    // 获取父节点,并进行一次左旋
                    x = parentOf(x);
                    rotateLeft(x);
                }
                // 情形5.1:如果父节点P是红色,且其叔父节点U是黑色或缺失,同时新增节点N是P的左孩子且P也是G的左孩子,
                // 此时将节点祖父节点G进行一次右旋,并替换G和P节点的颜色
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                // 祖父节点右旋
                rotateRight(parentOf(parentOf(x)));
            }
        } else {
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            // 情景3:父节点P和叔父节点U都为红色
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                // 情形4.2:如果父节点P是红色,而叔父节点U是黑色,且新增节点N是P的左孩子,P是G的右孩子,此种情况,需要将节点P进行一次右旋操作,然后按照情形5.2进行操作
                if (x == leftOf(parentOf(x))) {
                    x = parentOf(x);
                    // 父节点进行右旋
                    rotateRight(x);
                }
                // 情形5.2:如果父节点P是红色,且其叔父节点U是黑色或缺失,同时新增节点N是P的右孩子且P也是G的右孩子,此时将节点祖父节点G进行一次左旋,并替换G和P节点的颜色
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                // 以祖父节点做左旋
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    root.color = BLACK;
}

左旋转

ff0de0026633645002052e79e5d2ce79.gif

private void rotateLeft(Entry<K,V> p) {
    if (p != null) {
        // 获取节点右子树
        Entry<K,V> r = p.right;
        // 旋转节点右孩子指向右孩子的左子树
        p.right = r.left;
        // 右孩子的左子树非空则将旋转节点当做其父节点
        if (r.left != null)
            r.left.parent = p;
        // 右子树r“上浮”,因此将p的父节点指向新的节点R的父节点, 节点P“下沉”
        r.parent = p.parent;
        // 如果父节点为null,表示该节点是一个根节点
        if (p.parent == null)
            root = r;
        // 如果旋转的节点是父节点的左孩子,则将父节点的左孩子指向调整后的节点
        else if (p.parent.left == p)
            p.parent.left = r;
        else
            p.parent.right = r;
        // 将旋转的节点赋值给r节点的左孩子
        r.left = p;
        // 待旋转的节点下沉,其父节点指向旋转节点的右孩子
        p.parent = r;
    }
}

右旋

772eb4f59b7f5ba6da045f220a506d04.gif

private void rotateRight(Entry<K,V> p) {
    if (p != null) {
        // 获取待旋转节点左子树
        Entry<K,V> l = p.left;
        // 待旋转节点左子树指向待旋转左子树的右孩子
        p.left = l.right;
        // 左子树的右孩子非空,则将左子树的右孩子父亲指向待旋转的节点
        if (l.right != null)
            l.right.parent = p;
        // l节点被上沉,故将其父节点指向待旋转节点的父节点
        l.parent = p.parent;
        // 待旋转的父节点为空,表示是一个树的根节点
        if (p.parent == null)
            root = l;
        else if (p.parent.right == p)
            p.parent.right = l;
        else p.parent.left = l;
        l.right = p;
        p.parent = l;
    }
}

红黑树的删除原理及TreeMap的remove实现

删除步骤

  1. 将红黑树当做一颗二叉查找树,将节点删除
  2. 通过旋转、着色重新恢复成一颗红黑树

删除节点

  1. 如果删除的是红色的不会影响红黑树
  2. 如果删除的节点是黑色,则经过的路径黑色少了一个,违背红黑树原则

删除情况

  1. 如果删除的节点没有孩子,直接删除
  2. 删除的节点只有一个孩子,直接用孩子顶替
  3. 如果删除的节点存在两个孩子,则查找左子树的最大值或右子树的最小值,替换节点,然后转换成删除一个孩子的情况,如下图所示,删除x节点

94ec2db3d1aced352a336f2094ab178f.jpeg

删除节点x后,存在以下一些情况 情况1:被删除节点为根节点或者是红色节点 无需操作

情况2:N的兄弟节点sib是红色

f4dacfe4a590fd7a803c68683db6fe9f.jpeg 处理:

  1. 交换兄弟节点和其父节点的颜色
  2. 再将父节点左旋
  3. 设置父节点的右孩子为新的兄弟节点,此时被转换成情况3-1

90ad94acf6e3dc3c4039b2852ed9aaf7.jpeg

情况3:父节点可能是红色也可能是黑色 3-1:父节点红色的情况

08ed82dd69e1ad8c95c4ab0a84fda8cf.jpeg

处理:设置兄弟节点为红色,同时将删除替换的节点设置成其父类的节点,此时不满足节点是黑色的循环条件,跳出循环,最后将节点设置成黑色

9fc633d6ec7f52c84afc5ec8d268b471.jpeg

3-2:父节点黑色情况

193ead0374ef597d92beafd1a9a22a39.jpeg

处理:设置兄弟节点sib为红色,此时经过父节点的黑色节点数相同了,但不经过父节点的黑色节点比其多了一个,此时将经过父节点这颗子树当做一个整体进行循环处理

18c34ad880252f3eb4721583a12a269c.jpeg

情况4:删除后替换的N节点是父节点的左子树,兄弟节点的左孩子SL是红色、右孩子SR是黑色的。

57b58bef949db42e416855ba14c2c480.jpeg 处理:

  1. 交换叔父节点和叔父节点的左节点的颜色
  2. 再将兄弟节点进行右旋操作
  3. 重新设置sib节点将会转换成情况5再做处理

6c52ac2d5d30fdaee6614ca5482a47cd.jpeg

情况5:兄弟节点的右节点是红色,此时左节点可能是红色也可能是黑色的

629cd2815612841774ae594858933e6c.jpeg 处理

  1. Sib颜色设置成P的颜色,P和sib的右节点设置成黑色
  2. 父节点P左旋

adf4b96d4342b091fbc40009ab466914.jpeg

删除源码

public V remove(Object key) {
    // 根据key找到节点
    Entry<K,V> p = getEntry(key);
    if (p == null)
        return null;
    // 获取待删除节点的value值
    V oldValue = p.value;
    // 删除节点
    deleteEntry(p);
    // 返回删除节点的值
    return oldValue;
}

根据key获取节点

final Entry<K,V> getEntry(Object key) {
    // 使用自定义的比较器获取节点
    if (comparator != null)
        return getEntryUsingComparator(key);
    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;
}

获取右节点

static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
    if (t == null)
        return null;
    // 获取右节点
    else if (t.right != null) {
        Entry<K,V> p = t.right;
        // 循环获取左节点,直到不存在,此时返回了右节点最小key值得节点
        while (p.left != null)
            p = p.left;
        return p;
    } 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;
    }
}

删除节点

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

    // 待删除节点的左右节点都不为空
    if (p.left != null && p.right != null) {
        // 获取右孩子中节点最小key值的节点
        Entry<K,V> s = successor(p);
        // 将查找到的最小值的key和value赋值给待删除节点
        p.key = s.key;
        p.value = s.value;
        // 此时将删除节点指向删除右孩子的最小key值
        p = s;
    } // p has 2 children

    // Start fixup at replacement node, if it exists.
    // p节点指向右孩子最小key值的节点,此时不存在左节点了
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);

    if (replacement != null) {
        // 节点删除后将其孩子节点的父引用赋值为删除节点的父节点
        replacement.parent = p.parent;
        // 删除节点父节点为null,则将孩子节点设为树的根???
        if (p.parent == null)
            root = replacement;
        // 如果删除节点是其删除节点的左孩子,则将删除孩子的左孩子指向删除节点的孩子节点
        else if (p == p.parent.left)
            p.parent.left  = replacement;
        // 删除节点是删除节点的右孩子,则将删除孩子的右孩子指向删除节点的孩子节点
        else
            p.parent.right = replacement;

        // 删除节点待GC回收
        p.left = p.right = p.parent = null;

        // 如果删除的节点是黑色节点,此时破坏了红黑树的性质,需要进行平衡操作
        if (p.color == BLACK)
            fixAfterDeletion(replacement);
    // 删除的节点是根节点
    } else if (p.parent == null) {
        root = null;
    // 删除的节点不存在孩子
    } 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;
        }
    }
}

修复删除后节点颜色

// 此时节点x表示的是删除节点的替换节点,即删除节点的子节点,称为删除节点
private void fixAfterDeletion(Entry<K,V> x) {
    // 情景1: 被删除的节点为根节点或者颜色为红色,此时删除该节点不影响红黑树的特点。无需操作
    while (x != root && colorOf(x) == BLACK) {
        // 删除节点是父节点的左子树
        if (x == leftOf(parentOf(x))) {
            // 获取兄弟节点
            Entry<K,V> sib = rightOf(parentOf(x));

            // 经过情景2和情景3的处理后可以确保经过删除节点的父节点的黑色节点数相同,但此时不经过父节点的路径黑色数量多了一个
            // 此时循环这个调整过程,直到满足红黑树的条件,退出循环

            // 情景2:被删除节点为黑色,兄弟节点是为红色
            if (colorOf(sib) == RED) {
                // 替换兄弟节点和父节点颜色
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);

                // 将父节点左旋,父节点“下沉”,右孩子节点“上升”,使得经过新父节点的路径黑色节点数量相同。
                rotateLeft(parentOf(x));
                // 经过左旋操作后需要重新设置兄弟节点
                sib = rightOf(parentOf(x));
            }

            // 情景3:兄弟节点S和S的孩子都是黑色的,父节点可能是红色的也可能是黑色的
            if (colorOf(leftOf(sib))  == BLACK &&
                colorOf(rightOf(sib)) == BLACK) {
                // 设置兄弟节点为红色,此时经过父节点的路径黑色数量一样,但是不经过父节点的黑色多了一个
                setColor(sib, RED);
                // 将父节点这颗子树当做整体,此时如果父节点是红色的,则不满足循环条件,跳出。如果是黑色的则循环这个调整过程
                x = parentOf(x);
            } else {
                // 情形4.1:x节点是父节点的左子树,兄弟节点的左孩子SL是红色、右孩子SR是黑色的。
                if (colorOf(rightOf(sib)) == BLACK) {
                    setColor(leftOf(sib), BLACK); // 兄弟节点左节点设置为黑色
                    setColor(sib, RED); // 兄弟节点设为为红色
                    // 将兄弟节点右旋操作,父节点“下沉”,左孩子节点“上升”,使得经过兄弟节点的黑色数量一样
                    rotateRight(sib);
                    // 旋转后重新设置删除的兄弟节点,
                    sib = rightOf(parentOf(x));
                }
                // 情景5.1:兄弟节点的右节点是红色,此时左节点可能是红色也可能是黑色的
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(rightOf(sib), BLACK);
                // 经过上面的变色操作,P节点的右孩子黑色数量比左孩子多了一个,此时将删除节点做左旋操作,兄弟节点“上升”,父节点“下沉”,达到经过新父节点左右子树的黑色数量相等。
                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;
            }
        }
    }
    // 情景1:x是新的根,重新设置节点颜色为黑色
    setColor(x, BLACK);
}
⚠️ **GitHub.com Fallback** ⚠️