Map容器家族(TreeMap源码详解)

一、在Map集合家族的位置及概述

        TreeMap是一个有序的key-value集合,它内部是通过红-黑树实现的。TreeMap继承与AbstractMap,实现了NavigableMap接口,意味着它支持一系列的导航方法,比如返回有序的key集合。它还实现了Cloneable接口,意味着它能被克隆。另外也实现了Serializable接口,表示它支持序列化。TreeMap是基于红-黑树实现的,该映射根据其key的自然顺序进行排序,或者根据用户创建映射时提供的Comarator进行排序,另外,TreeMap是非同步的。

        不了红黑树,可以看我的这篇博客。

二、成员变量

    private final Comparator<? super K> comparator; // 保证有序比较器,默认自然排序

    private transient Entry<K,V> root;  // TreeMap中存储元素的红黑树根节点

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

    private transient int modCount = 0; // 修改次数

    private transient EntrySet entrySet;    // 存储所有键值对的集合
    private transient KeySet<K> navigableKeySet;
    private transient NavigableMap<K,V> descendingMap;

    private static final Object UNBOUNDED = new Object();

    private static final boolean RED   = false; // 红黑树的节点颜色--红色
    private static final boolean BLACK = true; // 红黑树的节点颜色--黑色

三、存储元素的红黑树节点

    // 红黑树的节点
    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;
        }

        ……
}

四、构造方法

    // 无参构造,默认自然排序
    public TreeMap() {
        comparator = null;
    }

    // 带比较器的构造器
    // 如果参数为null,则使用默认比较器
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    // 使用参数map集合,构造该map。
    // 排序方式:默认使用自然排序
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }

    // 使用SortedMap中的比较器,和其中的元素
    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) {
        }
    }

        其中的涉及到的方法:putAll方法buildFromSorted方法

1.putAll(Map<? extends K, ? extends V> map)

    // 将参数map中所有的元素添加到本集合中
    public void putAll(Map<? extends K, ? extends V> map) {
        int mapSize = map.size();   // 参数map中元素个数
        if (size==0 && mapSize!=0 && map instanceof SortedMap) {    // 本集合为空且参数集合有元素
            Comparator<?> c = ((SortedMap<?,?>)map).comparator();   // 获取参数的比较器
            if (c == comparator || (c != null && c.equals(comparator))) {   // 本集合的比较器和参数中的比较器相等
                ++modCount; // 修改次数自己1
                try {   // 构建
                    buildFromSorted(mapSize, map.entrySet().iterator(),
                                    null, null);
                } catch (java.io.IOException cannotHappen) {
                } catch (ClassNotFoundException cannotHappen) {
                }
                return; // 方法结束
            }
        }
        super.putAll(map);  // 如果不满足上述条件使用父类的putAll方法添加元素
    }

        当本集合的元素个数为0、参数map中有元素且和本集合的构造器相同,则使用buildFromSorted方法初始化本集合的红黑树(TreeMap的核心)。当不满足上诉条件时调用父亲的putAll方法:如下

    public void putAll(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }

分类这个putAll方法内使用增强for遍历entrySet键值对集合,在使用put方法,将元素添加进本集合;由于子类TreeMap本身重写了put方法,则在此处调用的是子类的put方法:如下

    // 实现父类的put方法
    public V put(K key, V value) {
        Entry<K,V> t = root;    // 根节点
        if (t == null) {    //当红黑树为空
            compare(key, key); // type (and possibly null) check    // 使用比较方法检查键不为null

            root = new Entry<>(key, value, null);   // 创建节点,并作为根节点
            size = 1;   // 集合元素个数为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;
        // 红黑树修复
        // 当有节点插入,则会破坏红黑树的5条性质,此时需要通过节点的颜色变换和左右旋转来使树满足性质
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

到这里putAll方法还没有结束,还有buildFromSorted方法。

2.buildFromSorted方法

        该方法有两个不同的重载方法,一个是构建根节点的,另一个是递归循环构建出左右子支的。

1)buildFromSorted(int size, Iterator<?> it, java.io.ObjectInputStream str, V defaultVal)方法

    // 线性时间树构建算法来自排序数据
    private void buildFromSorted(int size, Iterator<?> it,
                                 java.io.ObjectInputStream str,
                                 V defaultVal)
        throws  java.io.IOException, ClassNotFoundException {
        this.size = size;
        // 使用buildFromSorted构建出根节点
        root = buildFromSorted(0, 0, size-1, computeRedLevel(size),
                               it, str, defaultVal);
    }

2)buildFromSorted(int level, int lo, int hi, int redLevel, Iterator<?> it, java.io.ObjectInputStream str, V defaultVal)方法

       通过该方法的递归调用,巧妙的实现了用参数map集合中元素构建本集合的红黑树。在递归调用的时候,引入了二分查找的思想,巧妙的将红黑树的递归构建与二分查找的思想相结合。首先创建根节点,之后递归调用到左子分支的叶子结点,反复构建。相比现在看源码的时候以红黑树的递归创建为着眼节点,以二分查找的思想巧妙的实现递归调用。代码如下:

    private final Entry<K,V> buildFromSorted(int level, int lo, int hi,
                                             int redLevel,
                                             Iterator<?> it,
                                             java.io.ObjectInputStream str,
                                             V defaultVal)
        throws  java.io.IOException, ClassNotFoundException {
        /**
         * 策略:树的根节点是参数map集合最中间的元素。为了得到它,我们必须
         * 先递归构建根节点的整个左子分支,以便抓住它的所有元素。然后继续
         * 去构建右子分支。
         * 低位lo和高位hi参数是最小和最大值用于迭代遍历。他们不是
         * 真正的索引,只是顺序进行,确保提取的顺序与之相对应。
         */

        if (hi < lo) return null;   // 此方法的递归结束条件,低位大于等于高位结束

        int mid = (lo + hi) >>> 1;  // 无符号右移一位,等同于除2。计算机的位移操作比%2操作效率高

        Entry<K,V> left  = null;    // 左子树指针
        if (lo < mid)   // 当低位小于等于中间值时,递归循环构建 左子分支
            left = buildFromSorted(level+1, lo, mid - 1, redLevel,
                                   it, str, defaultVal);

        // extract key and/or value from iterator or stream
        K key;
        V value;

        // 如果遍历器不为空,则使用遍历器;否则使用流;
        if (it != null) {
            if (defaultVal==null) { // 默认值为空
                Map.Entry<?,?> entry = (Map.Entry<?,?>)it.next();   // 获取entry
                key = (K)entry.getKey();    // 获取键
                value = (V)entry.getValue();    // 获取值
            } else {    // 默认值不为空,使用默认值
                key = (K)it.next();
                value = defaultVal;
            }
        } else { // use stream
            key = (K) str.readObject();
            value = (defaultVal != null ? defaultVal : (V) str.readObject());
        }

        // 根据键值创建节点
        Entry<K,V> middle =  new Entry<>(key, value, null);

        // color nodes in non-full bottommost level red
        if (level == redLevel)  // 如果为红色层,则将节点变为红色。默认是黑色
            middle.color = RED;

        if (left != null) { // 左子树不为空,则链接上
            middle.left = left;
            left.parent = middle;
        }

        if (mid < hi) { // 递归遍历,得到右子树
            Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel,
                                               it, str, defaultVal);
            middle.right = right;
            right.parent = middle;
        }

        return middle;
    }

五、常用API

1.添加元素

        添加元素有两个方法,put和putAll方法,在上面的已经讲过,参见上面的讲解内容。

2.删除元素

    public void clear() {   // 一看见就懂
        modCount++;
        size = 0;
        root = null;
    }

    // 根据键删除键值对
    public V remove(Object key) {
        // 通过键获取键值对
        Entry<K,V> p = getEntry(key);
        if (p == null)  // 为空,则表示集合中没有该键值对,直接返回null
            return null;

        V oldValue = p.value;   // 获取旧值,并直接返回
        deleteEntry(p); // 删除操作
        return oldValue;
    }

其中涉及到的方法getEntry(Object key)、deleteEntry(Entry<K,V> p)

    // 根据键获取键值对
    final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        // 构造器不为空,使用getEntryUsingComparator方法
        // 为空跳过,使用下面方法
        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;    // 没找到返回空
    }

    // 通过键使用构造器获取键值对实例
    final Entry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
            K k = (K) key;  // 强制类型转换
        Comparator<? super K> cpr = comparator; // 构造器
        if (cpr != null) {  // 构造器不为空执行,否者返回null
            Entry<K,V> p = root;
            while (p != null) { // 循环遍历
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;    // 找不到返回null
    }
    // 删除节点并且平衡红黑树
    private void deleteEntry(Entry<K,V> p) {
        modCount++; // 修改次数加1
        size--; // 长度减1

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        // 如果严格内部,将后继元素复制到p,然后使p指向后继。
        // 只有待删除节点同时有两个孩子,才满足条件
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p);    // 获取后继节点
            p.key = s.key;  // 将后继节点的键替换待删除节点的键
            p.value = s.value;  // 将后继节点的值替换待删除节点的值
            p = s;  // 将栈中p变量指向堆中的s变量所指向的对象, 即将p指向已经需要移除的后继节点
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        // 开始修复被移除节点处的树结构
        // 如果p有左孩子,取左孩子,否则取右孩子
        // p现在指向的是待删除节点的后继节点(次大于待删除节点的),即它的右子分子的最左孩子,
        // 所以,p一定没有左孩子, 可能有右孩子
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
            // p节点没有父节点,即p节点是根节点
            if (p.parent == null)
                root = replacement;

            // p是其父节点的左孩子
            else if (p == p.parent.left)
                // 将p的父节点的left引用指向replacement
                // 这步操作实现了删除p的父节点到p节点的引用
                p.parent.left  = replacement;
            else
                // 如果p是其父节点的右孩子,将父节点的right引用指向replacement
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            // 解除p节点到其左右孩子和父节点的引用
            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 node.
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
            // 如果是黑色,调整树结构
            if (p.color == BLACK)
                fixAfterDeletion(p);

            // 这个判断也一定会通过,因为p.parent如果不是null则在上面的else if块中已经被处理
            if (p.parent != null) {
                // p是一个左孩子
                if (p == p.parent.left)
                    // 删除父节点对p的引用
                    p.parent.left = null;

                // p是一个右孩子
                else if (p == p.parent.right)
                    // 删除父节点对p的引用
                    p.parent.right = null;

                // 删除p节点对父节点的引用
                p.parent = null;
            }
        }
    }

    // 返回指定Entry的后继者,如果不是,则返回null。
    // 此处获取的后继节点是t节点的次大节点
    static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
        if (t == null)  // t为空直接返回null
            return null;
        else if (t.right != null) { // t的右孩子存在,遍历其右孩子到最左叶子节点,即为次大节点
            Entry<K,V> p = t.right;
            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 fixAfterDeletion(Entry<K, V> x) {
        // 循环处理,条件为x不是root节点且是黑色的
        while (x != root && colorOf(x) == BLACK) {
            // x是一个左孩子
            if (x == leftOf(parentOf(x))) {
                // 获取x的兄弟节点sib
                Entry<K, V> sib = rightOf(parentOf(x));

                // sib是红色的
                if (colorOf(sib) == RED) {
                    // 将sib设置为黑色
                    setColor(sib, BLACK);
                    // 将父节点设置成红色
                    setColor(parentOf(x), RED);
                    // 左旋父节点
                    rotateLeft(parentOf(x));
                    // sib移动到旋转后x的父节点p的右孩子
                    sib = rightOf(parentOf(x));
                }

                // sib的两个孩子的颜色都是黑色(null返回黑色)
                if (colorOf(leftOf(sib)) == BLACK &&
                        colorOf(rightOf(sib)) == BLACK) {
                    // 将sib设置成红色
                    setColor(sib, RED);
                    // x移动到x的父节点
                    x = parentOf(x);
                } else {    // sib的左右孩子都是黑色的不成立

                    // sib的右孩子是黑色的
                    if (colorOf(rightOf(sib)) == BLACK) {
                        // 将sib的左孩子设置成黑色
                        setColor(leftOf(sib), BLACK);
                        // sib节点设置成红色
                        setColor(sib, RED);
                        // 右旋操作
                        rotateRight(sib);
                        // sib移动到旋转后x父节点的右孩子
                        sib = rightOf(parentOf(x));
                    }

                    // sib设置成和x的父节点一样的颜色
                    setColor(sib, colorOf(parentOf(x)));
                    // x的父节点设置成黑色
                    setColor(parentOf(x), BLACK);
                    // sib的右孩子设置成黑色
                    setColor(rightOf(sib), BLACK);
                    // 左旋操作
                    rotateLeft(parentOf(x));
                    // 设置调整完的条件:x = root跳出循环
                    x = root;
                }
            } else { // symmetric// x是一个右孩子
                // 获取x的兄弟节点
                Entry<K, V> sib = leftOf(parentOf(x));

                // 如果sib是红色的
                if (colorOf(sib) == RED) {
                    // 将sib设置为黑色
                    setColor(sib, BLACK);
                    // 将x的父节点设置成红色
                    setColor(parentOf(x), RED);
                    // 右旋
                    rotateRight(parentOf(x));
                    // sib移动到旋转后x父节点的左孩子
                    sib = leftOf(parentOf(x));
                }

                // sib的两个孩子的颜色都是黑色(null返回黑色)
                if (colorOf(rightOf(sib)) == BLACK &&
                        colorOf(leftOf(sib)) == BLACK) {
                    // sib设置为红色
                    setColor(sib, RED);
                    // x移动到x的父节点
                    x = parentOf(x);
                } else {// sib的两个孩子的颜色都是黑色(null返回黑色)不成立
                    // sib的左孩子是黑色的,或者没有左孩子
                    if (colorOf(leftOf(sib)) == BLACK) {
                        // 将sib的右孩子设置成黑色
                        setColor(rightOf(sib), BLACK);
                        // sib节点设置成红色
                        setColor(sib, RED);
                        // 左旋
                        rotateLeft(sib);
                        // sib移动到x父节点的左孩子
                        sib = leftOf(parentOf(x));
                    }
                    // sib设置成和x的父节点一个颜色
                    setColor(sib, colorOf(parentOf(x)));
                    // x的父节点设置成黑色
                    setColor(parentOf(x), BLACK);
                    // sib的左孩子设置成黑色
                    setColor(leftOf(sib), BLACK);
                    // 右旋
                    rotateRight(parentOf(x));
                    // 设置跳出循环的标识
                    x = root;
                }
            }
        }

        // 将x设置为黑色
        setColor(x, BLACK);
    }

3.查询元素

    // 根据键获取值
    public V get(Object key) {
        // 调用getEntry方法获取值
        Entry<K, V> p = getEntry(key);
        // 如果p为空返回null,否则返回值
        return (p == null ? null : p.value);
    }

4.遍历操作

    // 返回包含所有键值对的Set视图集合
    public Set<Map.Entry<K, V>> entrySet() {
        EntrySet es = entrySet; // 获取成员变量entrySet值
        // 如果es为空,会创建EntrySet,在初次创建时会被初始化,以后就不用再初始化了
        return (es != null) ? es : (entrySet = new EntrySet());
    }

        EntrySet类为实现了AbstractSet的子类,其中的方法基本同父类。另外还有两种遍历方式:

        1)使用Set<K> keySet()方法

        2)使用Collection<V> values()方法

六、总结

       TreeMap的底层数据结构是红黑树,其基本的增删改查操作都是对红黑树的操作,在其本身的增删改查的基础上又扩展了一些其他方法。

posted @ 2018-11-06 21:30  小情绪Ango  阅读(283)  评论(0编辑  收藏  举报