美文网首页
[简单集合] TreeMap源码分析

[简单集合] TreeMap源码分析

作者: LZhan | 来源:发表于2020-01-16 16:46 被阅读0次

1 存储结构

红黑树

TreeMap只使用到了红黑树,所以时间复杂度为O(log n)。红黑树的特性为:

  • 每个节点是黑色或者红色
  • 根节点是黑色
  • 每个叶子节点(NIL)是黑色(注意:这里叶子节点,是指为空的叶子节点)
  • 如果一个节点是红色的,则它的子节点必须是黑色的
  • 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点

2 源码解析

2.1 属性

    // 比较器,如果没传则key要实现Comparable接口
    private final Comparator<? super K> comparator;
    // 根节点
    private transient Entry<K,V> root;

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

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

(1)comparator
按key的大小排序有两种方式,一种是key实现Comparable接口,一种方式通过构造方法传入比较器。

(2)root
根节点,TreeMap没有桶的概念,所有的元素都存储在一颗树中。

2.2 Entry内部类

典型的红黑树结构

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

2.3 构造方法

    // 默认构造方法,key必须实现Comparable接口
    public TreeMap() {
        comparator = null;
    }

    // 使用传入的comparator比较两个key的大小
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    // key必须实现Comparable接口,把传入map中的所有元素保存到新的TreeMap中
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }

    // 使用传入map的比较器,并把传入map中的所有元素保存到新的TreeMap中
    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) {
        }
    }

构造方法主要分成两类,一类是使用comparator比较器,一类是key必须实现Comparable接口。

2.4 get(Object key)方法

获取元素,典型的二叉查找树的查找方法。

    public V get(Object key) {
        // 根据key查找元素
        Entry<K,V> p = getEntry(key);
        // 找到了返回value值,没找到返回null
        return (p==null ? null : p.value);
    }

    final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        // 如果比较器不为空,使用带有comparator的版本获取元素
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key; // 将key强转为Comparable类型,如果没有传入comparator,key必须实现Comparable接口
        // 从根元素开始遍历
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                // 如果小于0从左子树查找
                p = p.left;
            else if (cmp > 0)
                // 如果大于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) {
            // 从根元素开始遍历
            Entry<K,V> p = root;
            while (p != null) {
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    // 如果小于0从左子树查找
                    p = p.left;
                else if (cmp > 0)
                    // 如果大于0从右子树查找
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
    }

(1)从root遍历整个树;
(2)如果待查找的key比当前遍历的key小,则在其左子树中查找;
(3)如果待查找的key比当前遍历的key大,则在其右子树中查找;
(4)如果待查找的key与当前遍历的key相等,则找到了该元素,直接返回;
(5)从这里可以看出是否有comparator分化成了两个方法,但是内部逻辑一模一样,因此可见笔者 comparator=(k1,k2)->((Comparable<?superK>)k1).compareTo(k2);这种改造的必要性。

2.5 put(K key,V value)方法

插入元素流程:

插入后再平衡流程:

插入后再平衡

(如果父节点是祖父节点的左节点)

情况 策略
1)父节点为红色,叔叔节点也为红色 (1)将父节点设为黑色;(2)将叔叔节点设为黑色;(3)将祖父节点设为红色;(4)将祖父节点设为新的当前节点,进入下一次循环判断;
2)父节点为红色,叔叔节点为黑色,且当前节点是其父节点的右节点 (1)将父节点作为新的当前节点(2)以新当节点为支点进行左旋,进入情况3);
3)父节点为红色,叔叔节点为黑色,且当前节点是其父节点的左节点 (1)将父节点设为黑色;(2)将祖父节点设为红色;(3)以祖父节点为支点进行右旋,进入下一次循环判断;

(如果父节点是祖父节点的右节点,则正好与上面反过来)

情况 策略
1)父节点为红色,叔叔节点也为红色 (1)将父节点设为黑色;(2)将叔叔节点设为黑色;(3)将祖父节点设为红色;(4)将祖父节点设为新的当前节点,进入下一次循环判断;
2)父节点为红色,叔叔节点为黑色,且当前节点是其父节点的左节点 (1)将父节点作为新的当前节点(2)以新当节点为支点进行右旋,进入情况3);
3)父节点为红色,叔叔节点为黑色,且当前节点是其父节点的右节点 (1)将父节点设为黑色;(2)将祖父节点设为红色;(3)以祖父节点为支点进行左旋,进入下一次循环判断;

实例:
我们依次向红黑树中插入 4、2、3 三个元素,来一起看看整个红黑树平衡的过程。

三个元素都插入完成后,符合父节点是祖父节点的左节点,叔叔节点为黑色,且当前节点是其父节点的右节点,即情况2)。

情况2)需要做以下两步处理:

(1)将父节点作为新的当前节点;

(2)以新当节点为支点进行左旋,进入情况3);

情况3)需要做以下三步处理:

(1)将父节点设为黑色;

(2)将祖父节点设为红色;

(3)以祖父节点为支点进行右旋,进入下一次循环判断;

下一次循环不符合父节点为红色了,退出循环,插入再平衡完成。

相关文章

  • [简单集合] TreeMap源码分析

    1 存储结构 TreeMap只使用到了红黑树,所以时间复杂度为O(log n)。红黑树的特性为: 每个节点是黑色或...

  • 深入ArrayList源码分析(JDK1.8)

    深入ArrayList源码分析(JDK1.8) Java 集合系列源码分析文章: 深入TreeMap源码解析(JD...

  • Java集合源码分析-TreeMap

    成员变量: 可以看出有一个排序器变量comparator,可以知道TreeMap是可以排序的(默认自然排序,或者自...

  • Java集合源码分析之Map(四):TreeMap

    TreeMap是红黑树的java实现,对红黑树不太了解的可以查阅这篇文章Java集合源码分析之基础(六):红黑树(...

  • TreeMap 源码分析

    前言 TreeMap作为可以对key或value进行大小排序的map,我们在开发中也会经常的用到,譬如说加密一串字...

  • TreeMap源码分析

    TreeMap 平衡二叉树 平衡二叉树(Self-balancing binary search tree)又被称...

  • TreeMap源码分析

    TreeMap简介 常见的数据结构有数组、链表,还有一种结构也很常见,那就是树。前面介绍的集合类有基于数组的Arr...

  • TreeMap源码分析

    ==重点: Key对象只有实现了Comparable接口,数据结构才是有序的,java 的默认数据类型都有实现,自...

  • TreeMap源码分析

    一.TreeMap的特性 TreeMap是有序的,可以自定义排序规则,如果不指定则按照默认的规则排序 二.Tree...

  • TreeMap 源码分析

    TreeMap实现了SortedMap接口,可以根据k的大小顺序,对map中的元素进行排序,可以根据key的自然顺...

网友评论

      本文标题:[简单集合] TreeMap源码分析

      本文链接:https://www.haomeiwen.com/subject/zyyfzctx.html