TreeMap源码

1、树的介绍

1.1、排序二叉树

HashMapHashSet的共同实现机制是哈希表,一个共同的限制是没有顺序。

TreeSetTreeMap这两个类的共同实现基础是排序二叉树

排序二叉树也是二叉树,但它没有重复元素,而且是有序的二叉树

  • 如果左子树不为空,则左子树上的所有节点都小于该节点

  • 如果右子树不为空,则右子树上的所有节点都大于该节点

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JqrTyfFQ-1582551196483)(images/07.png)]

1.2、常用算法

1.2.1、查找

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dHzNWwz2-1582551196484)(images/08.png)]

1.2.2、遍历

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uichGFNu-1582551196485)(images/09.png)]

1.2.3、插入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LNLWxJt4-1582551196486)(images/10.png)]

1.2.4、删除

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KmQ3zGLU-1582551196487)(images/11.png)]

1.3、平衡二叉树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BaP9KHlr-1582551196488)(images/12.png)]

1.5哈希与树的比较

与哈希表一样,树也是计算程序中一种最重要的数据结构和思维方式,为了能够快速操作数据,哈希是两种基本的思维方式,不需要顺序,,优先考虑哈希需要顺序,考虑树

除了容器类TreeMapTreeSet,数据库中的索引结构也是基于树的(不过基于B树,而不是二叉树),而索引是能够在大量数据中快速访问数据的关键

2、TreeMap的介绍

键有顺序

2.1、基本用法

TreeMap有两个基本构造方法

public TreeMap()

public TreeMap(Comparator<>)

  • 第一个默认构造方法,如果使用默认构造方法,要求Map中的键实现Comparable接口,TreeMap内部进行各种比较时会调用键的Comparable接口中的CompareTo方法
  • 第二个接受一个比较器对象comparator,如果comparator不为null,在TreeMap内部进行比较时会调用这个comparator的compare方法,而不再调用键的compareTo方法

应该使用哪一个?第一个更简单,但要求键实现Comparable接口,且期望的排序和键的比较结果是一致的;第二个更为灵活,不要求键实现Compstsble接口,比较器可以灵活复杂的方式进行

2.2、实现原理

TreeMap内部是使用红黑树实现的,红黑树是一种大致平衡的排序二叉树

TreeMap内部成员

 //comparator就是比较器,在构造方法中传递,如果没穿就是null
    private final Comparator<? super K> comparator;

    //root指向树的根节点,从根节点可以访问到每个节点
    private transient Entry<K,V> root = null;

 //当前节点数
    private transient int size = 0;

 private transient int modCount = 0;


 //红黑树中的节点
    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;//键
        V value;//值
        Entry<K,V> left = null;//左孩子
        Entry<K,V> right = null;//右孩子
        Entry<K,V> parent;//父节点
        boolean color = BLACK;//颜色

2.2、put方法

添加第一个节点

 public V put(K key, V value) {
        Entry<K,V> t = root;

        /*添加第一个节点             */
        if (t == null) {
            //为了检查key的类型和null,如果类型不匹配或为null,那么compare方法会抛出异常
            compare(key, key); // type (and possibly null) check
            //新建一个节点,指向根节点
            root = new Entry<>(key, value, null);
            size = 1;
            //用于迭代过程中检测结构性变化
            modCount++;
            return null;
        }
        /*添加第一个节点             */

向红黑树中添加节点

 //如果红黑树已经存在,会执行下面的代码
        //添加的关键是寻找父节点,寻找父节点根据是否设置了comparator分为两种情况
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        //设置了cpmomparator的情况;寻找父节点
        if (cpr != null) {
            //寻找是一个从根节点开始循环的过程中
            //在循环过程中
            //cmp:保存了比较结果;   t指向当前比较节点; parent为t的父节点;
            //循环结束后 parent就是要找的父节点
            /**
             * t一开始指向父亲节点,从根节点开始比较键,如果小于根节点,就将t设为左孩子。
             * 与左孩子比较,大于就与右孩子比较,就这样一直比较下去,直到t为null或者比较结果为0
             *
             * 如果比较结果为0,就表示已经有这个键,设置值,然后返还。
             *
             * 如果t为null,则退出循环时,parebnrt就指向待插入节点的父节点
             */
            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);
        }
        //没有设置comparator的寻找父节点
        //基本逻辑是一样的,当退出循环时parent指向父节点,只是如果没哟设置comparator
        //则假设key一定实现了Comparable接口,使用Comparanble接口的compareTo方法进行比较
        else {
            if (key == null)
                throw new NullPointerException();
            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;

稍微总结一下,其基本思想就是:循环比较找到父节点,并插入作为其左孩子或右孩子,然后调整保持树的大致平衡

2.3、根据键获取值get

 //根据键获取值
    public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }



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();
        //否则 假定key实现了Comparable的接口
        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;
    }

2.4、查看是否包含某个键

  public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }

2.5、查看是否包含某个值

TreeMap可以高效的按键进行查找,但如果要根据值进行查找,则需要遍历

//查看是否包含某个值
    //TreeMap可以高效的按键进行查找,但如果要根据值进行查找,则需要遍历
    public boolean containsValue(Object value) {
        //getFirstEntry()  :返回左下角的元素
        //successor返回给定节点的后继节点
        //从第一个节点开始,逐个比较,直到找到为止
        for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
            if (valEquals(value, e.value))//valEquals()就是比较值;
                return true;
        return false;
    }

//找后继节点
    //就是中序遍历
    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;
            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;
        }
    }



//getFirst返回第一个节点,左下角,不断往左下角找
    final Entry<K,V> getFirstEntry() {
        Entry<K,V> p = root;
        if (p != null)
            while (p.left != null)
                p = p.left;
        return p;
    }

 static final boolean valEquals(Object o1, Object o2) {
        return (o1==null ? o2==null : o1.equals(o2));
    }


2.6、根据键删除键值对 remove

//根据键删除键值对
    public V remove(Object key) {

        //根据key找到节点
        Entry<K,V> p = getEntry(key);

        if (p == null)
            return null;
        //待返回的值
        V oldValue = p.value;
        /**
         * 节点有三种情况:
         *
         *          叶子节点:直接修改父节点对应引用职位null即可
         *
         *          只有一个孩子: 在父亲节点和孩子节点直接建立连接
         *
         *          有两个孩子:先找到后继节点,找到后,替换当前节点的内容为后继节点,然后再删除后继节点(因为这个后继节点一定没有左孩子,所以就将两个孩子的情况转换为前面两种情况)
         *
         */


        deleteEntry(p);

        return oldValue;
    }





 /**
     * Delete node p, and then rebalance the tree.

    * 节点有三种情况:
        *
        *          叶子节点:直接修改父节点对应引用职位null即可
         *
                 *          只有一个孩子: 在父亲节点和孩子节点直接建立连接
         *
                 *          有两个孩子:先找到后继节点,找到后,替换当前节点的内容为后继节点,然后再删除后继节点(因为这个后继节点一定没有左孩子,所以就将两个孩子的情况转换为前面两种情况)
        *
     * */
    private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        //两个孩子的情况
        if (p.left != null && p.right != null) {
            //s为后继
            Entry<K,V> s = successor(p);
            //当前节点的key设置为后继的key和value
            p.key = s.key;
            p.value = s.value;
            //然后将待删除的节点p指向了s,这样就转换为了一个孩子或叶子节点的情况
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        //p为待删除节点,replaceMent为要替换p的孩子节点,主体代码就是在p的父节点
        //p.parent和replacement之间建立连接
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);
        //只有一个孩子的节点
        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;

            // Fix replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        }



        //叶子节点
        //具体分为两种情况:一种是删除最后一个节点,修改root为null
        else if (p.parent == null) { // return if we are the only node.
            root = null;
        }
        //另一种是根据带删除节点是父节点的左孩子还是右孩子,响应的设置孩子节点为null
        else { //  No children. Use self as phantom replacement and unlink.
            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;
            }
        }
    }


2.7、小结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-orMen3iq-1582551196489)(images/13.png)]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值