zoukankan      html  css  js  c++  java
  • TreeMap 源码分析

    TreeMap

    TreeMap 能解决什么问题?什么时候使用 TreeMap?

    1)TreeMap 是基于红黑树的 NavigableMap 接口实现类。
    2)TreeMap 根据键的自然顺序或构造时提供的比较器进行键的排序。
    3){@code containsKey}, {@code get}, {@code put} and {@code remove} 方法的时间复杂度为 log(n)。
    4)TreeMap 不是线程同步的,多线程并发访问 TreeMap 并且至少有一个线程修改了其结构,则它必须在外部实现同步。
    SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
    5)TreeMap 返回的所有集合视图都是快速失败的,在迭代器创建之后,如果不是通过迭代器自身的 remove 方法修改其结构,则将抛出 ConcurrentModificationException 异常。
    6)TreeMap 可以解决 topN 问题。
    

    如何使用 TreeMap?

    1)需要根据某种条件对映射进行排序时可以使用 TreeMap
    2)需要快速获取最大、最小值时可以使用 TreeMap
    3)需要实现按条件排序统计时可以使用 TreeMap
    

    使用 TreeMap 有什么风险?

    1)TreeMap 的底层是基于红黑树实现的,新增和读取操作的时间复杂度为 log(N),新增和删除时需要维护红黑树的平衡,比较耗性能。
    

    TreeMap 核心操作的实现原理?

    • 创建实例
        /**
         * 进行键排序的比较器,如果为 null,则使用自然顺序进行排序
         */
        private final Comparator<? super K> comparator;
    
        /**
         * 红黑树的根节点
         */
        private transient Entry<K,V> root;
    
        /**
         * TreeMap 中的元素总个数
         */
        private transient int size = 0;
    
        /**
         * TreeMap 被结构化修改的次数
         */
        private transient int modCount = 0;
    
        /**
         * 创建一个使用自然顺序进行键排序的空 TreeMap 实例
         */
        public TreeMap() {
            comparator = null;
        }
    
        /**
         * 创建一个使用指定比较器进行键排序的空 TreeMap 实例
         */
        public TreeMap(Comparator<? super K> comparator) {
            this.comparator = comparator;
        }
    
    • 添加元素
        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;
        }
    
        /**
         * 往 TreeMap 中添加新的键值对
         */
        @Override
        public V put(K key, V value) {
            // 读取根节点
            Entry<K,V> t = root;
            // 1)TreeMap 为空
            if (t == null) {
                /**
                 * 1)使用自然顺序进行键排序时,键是否实现了 Comparable 接口
                 * 2)键值是否为 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;
            // 读取键比较器
            final Comparator<? super K> cpr = comparator;
            // 1)键比较器不为 null,则使用比较器进行排序
            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);
            }
            // 2)键必须实现 Comparable 接口,并使用 Comparable#compareTo 方法进行比较
            else {
                if (key == null) {
                    throw new NullPointerException();
                }
                @SuppressWarnings("unchecked")
                // 将键转换为 Comparable 实例
                final
                Comparable<? super K> k = (Comparable<? super K>) key;
                do {
                    parent = t;
                    // 将键和当前节点的键进行比较
                    cmp = k.compareTo(t.key);
                    // 1)目标键小于节点键
                    if (cmp < 0) {
                        // 1-1)尝试比较其左子节点
                        t = t.left;
                        // 2)目标键大于节点键
                    } else if (cmp > 0) {
                        // 2-1)尝试比较其右子节点
                        t = t.right;
                        // 3)目标键已经存在
                    } else {
                        // 设置新值,返回旧值
                        return t.setValue(value);
                    }
                    // 已经无节点可比较
                } while (t != null);
            }
            // 创建新的节点,并设置其父节点
            final Entry<K,V> e = new Entry<>(key, value, parent);
            if (cmp < 0) {
                // parent 的左子节点更新为新增节点
                parent.left = e;
            } else {
                // parent 的右子节点更新为新增节点
                parent.right = e;
            }
            // 插入新元素后,平衡红黑树结构
            fixAfterInsertion(e);
            // 递增计数值
            size++;
            // 递增结构化修改计数值
            modCount++;
            return null;
        }
    
        // 平衡红黑树结构
        private void fixAfterInsertion(Entry<K,V> x) {
            // 新增的节点总是红色的
            x.color = TreeMap.RED;
            // 如果新增节点和其父节点都是红色的,则需要执行旋转
            while (x != null && x != root && x.parent.color == TreeMap.RED) {
                /**
                 *    xpp
                 *    /
                 * [红]xp
                 *
                 * 父节点在祖父节点的左侧
                 */
                if (TreeMap.parentOf(x) == TreeMap.leftOf(TreeMap.parentOf(TreeMap.parentOf(x)))) {
                    // 读取父节点的兄弟节点
                    final Entry<K,V> y = TreeMap.rightOf(TreeMap.parentOf(TreeMap.parentOf(x)));
                    /**
                     *      xpp
                     *    /     
                     * [红]xp    xpr[红色]
                     *
                     * 祖父节点的右子节点为 红色,则需要执行变色
                     */
                    if (TreeMap.colorOf(y) == TreeMap.RED) {
                        // 父节点设置为黑色
                        TreeMap.setColor(TreeMap.parentOf(x), TreeMap.BLACK);
                        // 父节点的兄弟节点设置为黑色
                        TreeMap.setColor(y, TreeMap.BLACK);
                        // 祖父节点设置为红色
                        TreeMap.setColor(TreeMap.parentOf(TreeMap.parentOf(x)), TreeMap.RED);
                        // 处理祖父节点,因为【父节点、叔叔节点、子节点已经是红黑树结构】
                        x = TreeMap.parentOf(TreeMap.parentOf(x));
                        // 2)兄弟节点不存在或为黑色
                    } else {
                        /**
                         *    xpp
                         *    /
                         * [红]xp
                         *    
                         *     x[红]
                         *
                         * x 在父节点的右侧
                         */
                        if (x == TreeMap.rightOf(TreeMap.parentOf(x))) {
                            // 读取父节点
                            x = TreeMap.parentOf(x);
                            // 基于父节点执行左旋
                            rotateLeft(x);
                        }
                        // 设置父节点为黑色
                        TreeMap.setColor(TreeMap.parentOf(x), TreeMap.BLACK);
                        // 设置祖父节点为红色
                        TreeMap.setColor(TreeMap.parentOf(TreeMap.parentOf(x)), TreeMap.RED);
                        // 基于祖父节点执行右旋
                        rotateRight(TreeMap.parentOf(TreeMap.parentOf(x)));
                    }
                    /**
                     *   xpp
                     *     
                     *      xp[红]
                     * 
                     * 父节点在祖父节点的右侧
                     */
                } else {
                    // 读取祖父节点的左孩子
                    final Entry<K,V> y = TreeMap.leftOf(TreeMap.parentOf(TreeMap.parentOf(x)));
                    /**
                     *      xpp
                     *    /     
                     * [红]xp    xpr[红色]
                     *
                     * 祖父节点的左子节点为红色,则需要执行变色
                     */
                    if (TreeMap.colorOf(y) == TreeMap.RED) {
                        TreeMap.setColor(TreeMap.parentOf(x), TreeMap.BLACK);
                        TreeMap.setColor(y, TreeMap.BLACK);
                        TreeMap.setColor(TreeMap.parentOf(TreeMap.parentOf(x)), TreeMap.RED);
                        x = TreeMap.parentOf(TreeMap.parentOf(x));
                    } else {
                        /**
                         *   xpp
                         *     
                         *      xp[红]
                         *      /
                         *     x[红]
                         * x 在父节点的左侧    
                         */
                        if (x == TreeMap.leftOf(TreeMap.parentOf(x))) {
                            x = TreeMap.parentOf(x);
                            // 基于父节点执行右旋
                            rotateRight(x);
                        }
                        // 设置父节点为黑色
                        TreeMap.setColor(TreeMap.parentOf(x), TreeMap.BLACK);
                        // 设置祖父节点为红色
                        TreeMap.setColor(TreeMap.parentOf(TreeMap.parentOf(x)), TreeMap.RED);
                        // 基于祖父节点执行左旋
                        rotateLeft(TreeMap.parentOf(TreeMap.parentOf(x)));
                    }
                }
            }
            // 根节点永远是黑色
            root.color = TreeMap.BLACK;
        }
    
        // https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
        private void rotateLeft(Entry<K,V> p) {
            if (p != null) {
                /**
                 * 读取中心节点的右侧子节点
                 */
                final Entry<K,V> r = p.right;
                /**
                 *    20
                 *   /  
                 * 10    40[红]
                 *      /   
                 *     30    50
                 *          /  
                 *       [红]45  60[红]
                 *          
                 *           47[红]
                 * 先执行 45、60、50 的变色,之后由于 40、50 都为红色,
                 * 执行以 20 为中心的左旋
                 */
                p.right = r.left;
                if (r.left != null) {
                    r.left.parent = p;
                }
                r.parent = p.parent;
                /**
                 * 1[p]
                 *  
                 *   2[红]
                 *    
                 *     3[红]
                 * 1)以 1 为中心的单节点左旋
                 */
                if (p.parent == null) {
                    root = r;
                    /**
                     *   3
                     *  /
                     * 1[红][p]
                     *  
                     *   2[红]
                     * 2)以 1 为中心的单节点左旋
                     */
                } else if (p.parent.left == p) {
                    // p的右侧子节点上升为p的父节点的左节点
                    p.parent.left = r;
                    /**
                     *   20
                     *  / 
                     * 10  30[p]
                     *      
                     *       40[红]
                     *        
                     *         50[红]
                     * 以 30 为中心的左旋
                     */
                } else {
                    // p的右侧子节点上升为p的父节点的右节点
                    p.parent.right = r;
                }
                // p下沉为p右侧子节点的左节点
                r.left = p;
                p.parent = r;
            }
        }
    
        // https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
        private void rotateRight(Entry<K,V> p) {
            if (p != null) {
                final Entry<K,V> l = p.left;
                p.left = l.right;
                /**
                 *
                 *        50
                 *       /   
                 *  [红]30     60
                 *     /  
                 *    10   40
                 *    / 
                 *[红]5   20[红]
                 *  /
                 * 3[红]
                 * 插入 3 时,先执行 5、20、10 的变色,变色后 10、30 都为红色,
                 * 执行以 50 为中心的右旋
                 */
                if (l.right != null) {
                    l.right.parent = p;
                }
                l.parent = p.parent;
                /**
                 *     3
                 *    /
                 *   2[红]
                 *  /
                 * 1[红]
                 * 以根节点为中心的单节点右旋
                 */
                if (p.parent == null) {
                    root = l;
                    /**
                     *  1
                     *   
                     *   3[红]
                     *  /
                     * 2[红]
                     * 以3为中心的单节点右旋
                     */
                } else if (p.parent.right == p) {
                    // p的左侧子节点上升为p的父节点的右节点
                    p.parent.right = l;
                } else {
                    // p的左侧子节点上升为p的父节点的左节点
                    p.parent.left = l;
                }
                // p节点下沉为p的左侧子节点的右节点
                l.right = p;
                p.parent = l;
            }
        }
    
    • 读取元素
        /**
         * 读取指定键关联的值,键不存在或其关联值为 null 时都返回 null。
         */
        @Override
        public V get(Object key) {
            final Entry<K,V> p = getEntry(key);
            return p==null ? null : p.value;
        }
    
        final Entry<K,V> getEntry(Object key) {
            if (key == null) {
                throw new NullPointerException();
            }
            // 1)使用指定的键比较器进行读取
            if (comparator != null) {
                return getEntryUsingComparator(key);
            }
            @SuppressWarnings("unchecked")
            // 键必须实现 Comparable 接口
            final
            Comparable<? super K> k = (Comparable<? super K>) key;
            // 读取根节点
            Entry<K,V> p = root;
            while (p != null) {
                // 将目标键和当前树节点进行比较
                final 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")
            final
            K k = (K) key;
            final Comparator<? super K> cpr = comparator;
            // 读取根节点
            Entry<K,V> p = root;
            while (p != null) {
                // 使用键比较器比较目标键和节点键
                final 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;
        }
    
    • 读取最小键
        /**
         * 读取键最小的键值对
         */
        public Map.Entry<K,V> firstEntry() {
            return exportEntry(getFirstEntry());
        }
    
        /**
         * 返回简单的不可变键值对,以防止影响源 TreeMap 中的键值对
         */
        static <K,V> Map.Entry<K,V> exportEntry(TreeMap.Entry<K,V> e) {
            return e == null ? null :
                new AbstractMap.SimpleImmutableEntry<>(e);
        }
    
        /**
         * 最小键位于左子树的最左侧叶子节点
         */
        final Entry<K,V> getFirstEntry() {
            Entry<K,V> p = root;
            if (p != null) {
                while (p.left != null) {
                    p = p.left;
                }
            }
            return p;
        }
    
    • 读取最大键
        /**
         * 读取最大键关联的键值对
         */
        public Map.Entry<K,V> lastEntry() {
            return exportEntry(getLastEntry());
        }
    
        /**
         * 最大键位于右子树的最右叶子节点
         */
        final Entry<K,V> getLastEntry() {
            Entry<K,V> p = root;
            if (p != null) {
                while (p.right != null) {
                    p = p.right;
                }
            }
            return p;
        }
    
    • 小于或小于等于指定键的最大键:lowerEntry、floorEntry
        /**
         *  获取小于目标 key 的最大键
         */
        public Map.Entry<K,V> lowerEntry(K key) {
            return exportEntry(getLowerEntry(key));
        }
    
        /**
         *  返回小于目标 key 的最大键,如果不存在,则返回 null
         */
        final Entry<K,V> getLowerEntry(K key) {
            // 读取根节点
            Entry<K,V> p = root;
            // 树不为空
            while (p != null) {
                // 比较目标键与当前键
                final int cmp = compare(key, p.key);
                // 目标键比较大
                if (cmp > 0) {
                    // 读取当前节点的右子节点
                    if (p.right != null) {
                        // 往右侧查找
                        p = p.right;
                    } else {
                        // 已经没有更大的值,则直接返回
                        return p;
                    }
                    // 目标键比较小
                } else {
                    // 读取当前节点的左子节点
                    if (p.left != null) {
                        p = p.left;
                    } else {
                        // 目标键小于当前节点并且已经没有左子节点,则需要回溯到父节点
                        Entry<K,V> parent = p.parent;
                        Entry<K,V> ch = p;
                        // 当前节点在父节点的左侧,则一直回溯到其位于父节点的右侧为止
                        while (parent != null && ch == parent.left) {
                            ch = parent;
                            parent = parent.parent;
                        }
                        // 当前节点在父节点的右侧,则直接返回父节点
                        return parent;
                    }
                }
            }
            return null;
        }
    
        /**
         *  获取小于等于目标 key 的最大键
         */
        public Map.Entry<K,V> floorEntry(K key) {
            return exportEntry(getFloorEntry(key));
        }
    
        /**
         *  返回小于等于目标 key 的最大键,如果不存在,则返回 null
         */
        final Entry<K,V> getFloorEntry(K key) {
            Entry<K,V> p = root;
            while (p != null) {
                final int cmp = compare(key, p.key);
                if (cmp > 0) {
                    if (p.right != null) {
                        p = p.right;
                    } else {
                        return p;
                    }
                } else if (cmp < 0) {
                    if (p.left != null) {
                        p = p.left;
                    } else {
                        Entry<K,V> parent = p.parent;
                        Entry<K,V> ch = p;
                        while (parent != null && ch == parent.left) {
                            ch = parent;
                            parent = parent.parent;
                        }
                        return parent;
                    }
                // 如果目标键和当前节点相等,则直接返回   
                } else {
                    return p;
                }
            }
            return null;
        }
    
    • 大于或大于等于指定键的最小键:higherEntry、ceilingEntry
        /**
         *  读取大于目标键的最小键
         */
        public Map.Entry<K,V> higherEntry(K key) {
            return exportEntry(getHigherEntry(key));
        }
    
        /**
         *  读取大于目标键的最小键
         */
        final Entry<K,V> getHigherEntry(K key) {
            Entry<K,V> p = root;
            while (p != null) {
                final int cmp = compare(key, p.key);
                // 目标键小于当前节点
                if (cmp < 0) {
                    // 往左读取其子节点
                    if (p.left != null) {
                        p = p.left;
                    } else {
                        // 已经没有左子节点,则直接返回
                        return p;
                    }
                    // 目标键大于当前节点
                } else {
                    // 往右读取其子节点
                    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;
                    }
                }
            }
            return null;
        }
    
        /**
         *  读取大于等于目标键的最小键
         */
        public Map.Entry<K,V> ceilingEntry(K key) {
            return exportEntry(getCeilingEntry(key));
        }
    
        /**
         *  读取大于等于目标键的最小键,如果不存在,则返回 null
         */
        final Entry<K,V> getCeilingEntry(K key) {
            Entry<K,V> p = root;
            while (p != null) {
                final 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;
        }
    
    • 替换值
        /**
         *  如果键存在则替换值,并返回旧值,键不存在则返回 null
         * created by ZXD at 29 Nov 2018 T 22:36:47
         * @param key
         * @param value
         * @return
         */
        @Override
        public V replace(K key, V value) {
            final Entry<K,V> p = getEntry(key);
            if (p!=null) {
                final V oldValue = p.value;
                p.value = value;
                return oldValue;
            }
            return null;
        }
    
        /**
         *  如果目标键存在,并且关联值为 oldValue,则将其替换为 newValue,
         *  替换成功返回 true,否则返回 false。
         * created by ZXD at 29 Nov 2018 T 22:38:11
         * @param key
         * @param oldValue
         * @param newValue
         * @return
         */
        @Override
        public boolean replace(K key, V oldValue, V newValue) {
            final Entry<K,V> p = getEntry(key);
            if (p!=null && Objects.equals(oldValue, p.value)) {
                p.value = newValue;
                return true;
            }
            return false;
        }
    
        /**
         *  使用函数式接口基于旧键和旧值计算新值,并替换旧值
         * created by ZXD at 29 Nov 2018 T 22:41:51
         * @param function
         */
        @Override
        public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
            Objects.requireNonNull(function);
            final int expectedModCount = modCount;
            // 从小到大遍历每一个节点
            for (Entry<K, V> e = getFirstEntry(); e != null; e = TreeMap.successor(e)) {
                // 使用函数式接口基于旧键和旧值计算新值,并替换旧值
                e.value = function.apply(e.key, e.value);
                // fast-fail 机制保证并发修改时快速失败
                if (expectedModCount != modCount) {
                    throw new ConcurrentModificationException();
                }
            }
        }
    
        /**
         *  读取当前节点的后继节点
         */
        static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
            // 1)当前节点为 null
            if (t == null) {
                return null;
            // 2)当前节点存在右侧子节点,则找到右子节点的最左侧子节点,如果没有左子节点,则直接返回  
            } else if (t.right != null) {
                Entry<K,V> p = t.right;
                while (p.left != null) {
                    p = p.left;
                }
                return p;
            // 3)当前节点无右子节点,则需要往左上回溯到其父节点    
            } 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;
            }
        }
    
  • 相关阅读:
    IE兼容只读模式
    关于css实现单行、多行省略标记
    table表格字母无法换行
    IE浏览器兼容background-size
    IE兼容rgba()透明度
    修改输入框placeholder的默认样式
    Swarm+Docker+Portainer(集群,图形化)
    Docker的centos镜像内无法使用systemctl命令的解决办法
    Docker 制作Nginx镜像
    Xtrabackup 全量备份脚本
  • 原文地址:https://www.cnblogs.com/zhuxudong/p/10041471.html
Copyright © 2011-2022 走看看