• 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的底层数据结构是红黑树,其基本的增删改查操作都是对红黑树的操作,在其本身的增删改查的基础上又扩展了一些其他方法。

  • 相关阅读:
    【笔记】进化型开发方法
    错误注入学习笔记
    【C/C++】关于编译错误 "error C2146: syntax error : missing ';' before identifier 'xxx'"
    查找进程加载到内存中的EntryPoint
    devepxress qtp 点击子菜单
    RijndaelManaged 自定义key和iv
    sql server transaction
    使用gzip压缩字符串
    tsql 与时间(周)相关的一些操作
    excel 合并单元格
  • 原文地址:https://www.cnblogs.com/IdealSpring/p/11871184.html
Copyright © 2020-2023  润新知