HashMap源码之二:put方法

2018-08-02  本文已影响22人  涂豪_OP

    上一篇讲了HashMap的概述,这次研究下HashMap的重要方法put;不管什么容器类,添加元素的方法总是重要的,不能忽略的:

    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key key的hash值
     * @param key the key   键值对中的键
     * @param value the value to put    键值对中的值
     * @param onlyIfAbsent if true, don't change existing value 当存入一个元素是,如果此元素
     *                     已经存在,onlyIfAbsent指的是是否替换已经存在的元素,如果他为true,就不替
     *                     换,否则替换;默认为true,替换之
     * @param evict if false, the table is in creation mode.    evict是指当前的hashmap是不是
     *              处于创建状态,默认是true,代表hashmap的table创建好了,可以put元素了
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        //tab是HashMap的数组;要插入一个元素,首先要计算出这个元素应该放入数组的哪个位置;位置确定
        //后,那个位置,可能已经存在一个元素,这时候就需要用待插入元素去更新原来的元素,或者在那个元素
        //后面插入;p就是那个原来的元素,当然,他可能为空;n是数组的长度;i是待插入的元素在数组上的索引
        Node<K,V>[] tab; Node<K,V> p; int n, i;

        //如果当前的数组没有创建,,或者数组里面没有元素,那么调用resize()方法
        //resize()方法的作用是初始化HashMap的数组或者对HashMap进行扩容。
        //resize()是一个非常重要的方法,一会详细分析
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

        //元素插入桶的位置是由数组长度和key的hash值共同决定的,把这两个值进行与
        //操作就能得出桶的索引;如果那个数组索引上没有元素,那么创建一个新的节点
        //并放入数组中,这样就完成了插入;newNode()非常简单,不单独分析。
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

        //如果i那个位置上已经存在元素,说明发生了hash碰撞,需要更新i这
        //个位置连接的红黑树/链表上的节点;或者在红黑树/链表上新增新的元素
        else {
            Node<K,V> e; K k;

            //因为两个不同的key可能会hash出同一个hash值,这个时候就产生hash碰撞了;发生碰撞的两个元素
            //,他们的key可能一样,也可以不一样;如果一样的话,直接更新好了;如果不一样,那么只能把待插入
            //的元素插入的p的后面,(k = p.key) == key || (key != null && key.equals(k))就是
            //判断两个元素的key是不是相等。
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))

                //key相等的话就把p指向e,后面用得上
                e = p;

            //如果key不等,说明待插入的元素要插入到p的后面,这个p可能是红黑树中的一个节点,也有可能是链表
            //中的一个节点,p后面连接不同的数据结构,插入的方法是不一样的,这里首先判断p后面是不是红黑树
            else if (p instanceof TreeNode)

                //以红黑树的方式插入待插入元素
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

            //这个分支代表p后面连接这个一个链表,以链表的方式插入目标元素
            else {

                //链表的插入就非常简单了,就是遍历该链表,然后一个个比较,必要的时候树化
                //当然了,链表中的节点的key可能和待插入元素的key一样,如果一样的话,直
                //接更新链表上的这个节点即可;如果没有一样的,那么在链表尾部插入该元素
                //插入完成后,e就指向了空
                for (int binCount = 0; ; ++binCount) {

                    //遍历到链表的尾部还没有发现有元素和目标元素的key一样,那么根据传进来的key和value创
                    //建一个全新节点插入链表尾部;注意,if里面已经对e赋值了,所以第二个if可以直接对e操作
                    if ((e = p.next) == null) {

                        //创建全新的节点并插入节点的尾部
                        p.next = newNode(hash, key, value, null);

                        //如果链表节点数量达到了阈值(8) - 1,那么调用treeifyBin进行链表的树化
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }

                    //这里就是判断目标元素和链表中的节点的key值是否一样,如果一样就直接更新,简单
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }

            //如果e不为空,说明进入了第一个else分支,第一个else分支是指数组中已经存在一个key的hash和
            //目标元素的key的hash相等的节点,如果e不为空,说明整个put其实就是更新老的节点
            if (e != null) { // existing mapping for key

                //获取老的值
                V oldValue = e.value;

                //onlyIfAbsent是传进来的,默认是false;所以进入if
                if (!onlyIfAbsent || oldValue == null)

                    //简单的赋值
                    e.value = value;

                //afterNodeAccess在HashMap中是一个空实现,
                //在LinkedHashMap里面倒是有实现,暂时不管
                afterNodeAccess(e);
                return oldValue;
            }
        }

        //修改次数+1,HashMap是线程不安全的,这玩意就是为
        //了解决迭代的时候修改HashMap造成的异常,后面分析
        ++modCount;

        //如果HashMap的元素个数超过阈值,就调用resize()进行扩容
        if (++size > threshold)
            resize();
        //同afterNodeAccess,不管
        afterNodeInsertion(evict);
        return null;
    }

    纵观put方法,最重要,也是最难理解的是扩容方法resize()和树化方法treeifyBin(//参数略),在分析这两个方法之前,先研究下红黑树节点是怎么被插入红黑树中的:

        /**
         * Tree version of putVal.
         * 参数解释:
         *  1.map : HashMap,简单
         *  2.tab : HashMap中的数组,简单
         *  3.h : key的hash值
         *  4.k : key
         *  5.v : value
         */
        final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                                       int h, K k, V v) {
            //key的Class对象
            Class<?> kc = null;

            //searched是一个一次性开关,仅对根节点生效,下面会解释
            boolean searched = false;

            //putTreeVal()是TreeNode的方法,这个类有个成员变量parent,代表这个节
            //点的父节点如果父节点不为空的话,那么先找到红黑树的根节点,root()方法就是
            //往死里遍历红黑树,最终找到根节点;如果父节点为空,说明他本身就是根节点。
            TreeNode<K,V> root = (parent != null) ? root() : this;

            //从根节点开始遍历红黑树
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;

                //如果遍历到的节点的hash值大于目标key的hash
                //值,其实节点的hash值就是key的hash值
                if ((ph = p.hash) > h)

                    //如果遍历到的节点的key的hash目标元素的hash,那么说明
                    //这个元素应该插入到遍历到的节点的左边,此时将dir置成-1
                    dir = -1;

                //反之置成1
                else if (ph < h)
                    dir = 1;

                //如果两个hash相等,key也相等,那么返回这个节点;看起来有点奇怪,怎么就
                //直接返回了呢?新的value没有更新啊,其实在putVal的最后面已经进行了更新
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;

                //流程走到这里,说明待插入的元素的key的hash和当前节点的hash相等但是两个key本身不相等(此时就
                //是发生了hash碰撞)。comparableClassFor的作用就是返回key的Class信息;如果key的类型实现了
                //Comparable接口,那么返回key的类型信息,没有实现的话就返回null;因为红黑树的左右子节点之间的
                //值是有大小之分的,要想有大小之分,就必须具有可比性,实现Comparable接口会让两个相同类型的变量
                //之间可比;如果返回null,说明key没有可比性;dir = compareComparables(kc, k, pk)) == 0
                //的意思是k和pk之间不可比,也就说待插入元素的key和节点的key之间不具有可比性
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {

                    //在进for循环之前,searched为false;当遍历到根节点时,进入if,if里面通过find
                    //方法搜索了整棵树,如果能找到目标节点当然更好,找不到就再调用tieBreakOrder找一次
                    if (!searched) {

                        TreeNode<K,V> q, ch;

                        //把searched置为true,在遍历根节点的所有子节点时,都不会进这个分支
                        searched = true;

                        //find方法就是在红黑树里面查找key和目标元素key相等的节点
                        //这里分左右子树分别递归查找,短路操作是为了在左子树中找到
                        //后无需在右子树中再次查找;如果找到了,就把这个节点直接返回
                        if (((ch = p.left) != null &&
                             (q = ch.find(h, k, kc)) != null) ||
                            ((ch = p.right) != null &&
                             (q = ch.find(h, k, kc)) != null))
                            return q;
                    }

                    //流程走到这里,说明还没有找到目标节点,也就是说目标元素的key和红黑树的节点的key的hash相等
                    //但是两个key本身之间无法比较;那就只能使出绝招了:tieBreakOrder;这个方法是调用系统的方
                    //法identityHashCode来比较,比较的也是hash,但是不是上面的hash了,感兴趣的可以了解下
                    dir = tieBreakOrder(k, pk);
                }

                //流程走到这,说明key的比较结果出来了,这就要插入了;只有当
                //前节点没有子节点的时候才能插入,否则就要进行下一轮循环比较了

                //把遍历到的节点赋值给xp,这里是把xp当做一个父
                //节点来使用的,因为后面的流程会把p指向他的子节点
                TreeNode<K,V> xp = p;

                //如果dir小于0,说明目标元素要放在p的左子树,所以把p指向他的左子节点
                //如果dir大于0,说明目标元素要放在p的右子树,所以把p指向他的右子节点
                //如果p不为空,说p已经有左/右子节点了,拿到这个节点,开始下一轮循环
                //如果p为空,说明已经循环到叶子节点了,这时候就要把目标元素插在叶子节点下面
                if ((p = (dir <= 0) ? p.left : p.right) == null) {

                    //next是Node的属性,TreeNode是Node的子类,继承了这个属性;
                    //这个属性记录了树化之前链表上节点的顺序,估计是为了反树化准备的
                    Node<K,V> xpn = xp.next;

                    //创建一个叶子节点,newTreeNode方法比较简单,不多说,注
                    //意最后一个参数;也就是说,每个红黑树节点都有一个next属性
                    TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);

                    //决定是插在左子树还是右子树
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;

                    //将刚才创建的节点x赋值给他父节点的next属性
                    xp.next = x;

                    //将父节点赋值给刚才创建的子节点的parent和prev
                    x.parent = x.prev = xp;

                    //xpn是xp的next,xp又是遍历到的节点,所xpn是当前遍历到的节点的next果当前
                    //遍如历到的节点的next不为空,那么把当前遍历到的节点的prev指向刚才创建的节点
                    if (xpn != null)
                        ((TreeNode<K,V>)xpn).prev = x;

                    //修复红黑树的平衡性,比较复杂,暂时不管
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null;
                }
            }
        }

    可以看到,putTreeVal方法还是有点复杂的;下面研究扩容方法:

    /**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     *
     * @return the table
     */
    //扩容的方法,还肩负着初始化HashMap的重任
    final Node<K,V>[] resize() {
        //扩容前老的数组,当然如果是初始化HashMap的话,这个数组就是空了
        Node<K,V>[] oldTab = table;

        //老数组的长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;

        //老的扩容阈值
        int oldThr = threshold;

        //新数组的长度和扩容阈值
        int newCap, newThr = 0;

        //如果oldCap大于0,说明调用resize()是为了扩容
        if (oldCap > 0) {

            //若干老数组的长度超过了最大值,那么扩容失败
            //;直接返回老数组,并且把阈值设置成最大值
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }

            //如果没有超过最大值,那么新数组的长度是老数组长度的两个倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                //阈值也翻倍
                newThr = oldThr << 1; // double threshold
        }

        //如果oldCap == 0 ,说明是创建HashMap,oldThr
        //大于0的话,说明调用带一个参数的HashMap的构造函数
        else if (oldThr > 0) // initial capacity was placed in threshold

            //对于带一个参数的构造函数,HashMap的容量就是扩容阈值
            newCap = oldThr;

        //种子情况就是最常见的了;HashMap map = new HashMap(),啥都没指定
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }

        //对于一个参数的构造函数,只确定了容量,并没有确定新的扩容阈值
        if (newThr == 0) {
            //计算新的扩容阈值,简单
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }

        //不管是扩容还是调用某个构造函数创建HashMap,走到这,新的扩容阈值算是定下来了,赋值
        threshold = newThr;

        //创建数组,这个数组对HashMap的重要性无需多言
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];

        //把新数组指向刚才创建的数组
        table = newTab;

        //如果老数组不为空,那么扩容吧
        if (oldTab != null) {
            //遍历老数组,把数组元素拷到新数组里面
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;

                //取出节点赋值给e
                if ((e = oldTab[j]) != null) {

                    //把老数组元素置空
                    oldTab[j] = null;

                    //如果e没有后继节点,那么将此节点存入新数组,说明这个桶只有一个元素,没有链表和红黑树
                    //,e.hash & (newCap - 1)就是计算数组的索引;注意哦,这里是移动节点,所以是拿节
                    //点的hash与数组长度 - 1进行与运算确定这个节点位于数组中的索引;而上面putVal方法操
                    //作的是元素,所以是拿元素的key的hash与数组长度 - 1进行与运算确定元素在数组中的索引
                    //ps:其实这两个hash是一样的
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    //如果桶里面装的元素是红黑树节点
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    //走到这个分支,说明这个桶后面接的是链表
                    else { // preserve order

                        //引入了两个链表:low、high;loHead是低位链表low的头结点,loTail是low的尾节点
                        //hiHead是高位链表high的头结点,hiTail是尾节点;这两个链表是为了拆分老数组某个桶
                        //后面连接的链表用的
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;

                        //循环获取链表中的节点
                        do {
                            next = e.next;
                            //这个操作猛如虎:oldCap是2的n次幂,形如100000......;e.hash & oldCap就是
                            //取e.hash的高位,相与的结果要么是e.hash的高位,要么是0;如果是0的话,进入if
                            if ((e.hash & oldCap) == 0) {

                                //如果链表尾节点都为空,说明这个链表是空的,此时将e置为头结点
                                if (loTail == null)
                                    loHead = e;

                                //否则的话,就把e放到链表尾部
                                else
                                    loTail.next = e;

                                //同时把链表尾节点指向e
                                loTail = e;
                            }
                            //如果相与结果不为0,那么就是e.hash的高位,进入else
                            else {
                                //道理同上
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);

                        //如果低位链表的尾节点不为空,那么将尾节点的后继节点置空
                        if (loTail != null) {
                            loTail.next = null;

                            //同时将低位节点放入第j个桶
                            newTab[j] = loHead;
                        }

                        //同上,不过是把高位节点放入低j + oldCap个桶
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        //返回新数组
        return newTab;
    }

    方法本身不算难,他的扩容思想如下:
    1.如果桶后面没接红黑树/链表,那么拿到这个节点的hash,与新数组的长度 - 1做与运算,确定桶的位置,然后把这个节点放入那个桶;
    2.如果桶后面接着一个链表,那么将此链表一拆为二;拆分的依据是用节点的hash与老数组做与运算,与运算只有两个结果,要么是0,要么是hash的高位;如果结果是0,那么此节点插入低位链表;如果结果不等于0,那么将此插入高位链表;最后把低位链表放入第j个桶,把高位链表放入第j + oldCap个桶;
    3.如果桶后面接着的是一棵树,那么调用split方法将红黑树拆分,下面研究下这个方法:

        /**
         * Splits nodes in a tree bin into lower and upper tree bins,
         * or untreeifies if now too small. Called only from resize;
         * see above discussion about split bits and indices.
         *
         *  拆分的思想是先将红黑树转化成两个链表;这两个链表必要的时候进行树化和反树化
         *
         * @param map the map   HashMap,没问题
         * @param tab the table for recording bin heads     新数组
         * @param index the index of the table being split  桶的位置
         * @param bit the bit of hash to split on   老数组的长度
         */
        final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {

            //红黑树的根节点
            TreeNode<K,V> b = this;

            // Relink into lo and hi lists, preserving order
            //引入四个红黑树节点,类似于链表的拆分,高低位的头尾节点
            TreeNode<K,V> loHead = null, loTail = null;
            TreeNode<K,V> hiHead = null, hiTail = null;

            //高低位节点的计数器
            int lc = 0, hc = 0;

            //通过节点的next往死里遍历红黑树,但是for循环可以这样用的吗
            for (TreeNode<K,V> e = b, next; e != null; e = next) {

                //重新赋值next节点
                next = (TreeNode<K,V>)e.next;

                //把e的next置空,因为e要插到两个链表中的一个区,他的后继节点还不确定,所以置空
                e.next = null;

                //与操作,根链表的拆分是一毛一样的
                if ((e.hash & bit) == 0) {

                    //如果低位尾节点是空,说明链表是空的,那么e就是头结点了
                    if ((e.prev = loTail) == null)
                        loHead = e;

                    //否则的话就插在尾节点后面
                    else
                        loTail.next = e;

                    //同时把尾节点指向e
                    loTail = e;

                    //低位节点数量+1
                    ++lc;
                }
                //同上
                else {
                    if ((e.prev = hiTail) == null)
                        hiHead = e;
                    else
                        hiTail.next = e;
                    hiTail = e;
                    ++hc;
                }
            }

            //经过上面的遍历,红黑树成功被拆成高低位两个链表,但是这另个链表的长度不一定
            //符合树化和反树化的要求,所以这里就进行判断;如果小于反树化的阈值,那么把链
            //表中的红黑树节点TreeNode转化成普通节点Node;否则的话就把链表树化成红黑树
            if (loHead != null) {
                if (lc <= UNTREEIFY_THRESHOLD)
                    tab[index] = loHead.untreeify(map);
                else {
                    tab[index] = loHead;
                    if (hiHead != null) // (else is already treeified)
                        loHead.treeify(tab);
                }
            }
            if (hiHead != null) {
                if (hc <= UNTREEIFY_THRESHOLD)
                    tab[index + bit] = hiHead.untreeify(map);
                else {
                    tab[index + bit] = hiHead;
                    if (loHead != null)
                        hiHead.treeify(tab);
                }
            }
        }

    拆分红黑树其实和拆分链表的道理一样,还算是比较简单的,下面介绍最后一个重要方法,树化:treeifyBin(Node<K,V>[] tab, int hash):

/**
     * Replaces all linked nodes in bin at index for given hash unless
     * table is too small, in which case resizes instead.
     * 树化的第一步是将普通节点Node转化成红黑树节点
     */
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        //如果HashMap的容量没有达到MIN_TREEIFY_CAPACITY,那么优先选择扩容
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();

        //下面就为树化做准备
        else if ((e = tab[index = (n - 1) & hash]) != null) {

            //申明头尾两个红黑树节点
            TreeNode<K,V> hd = null, tl = null;

            //往死里遍历链表
            do {
                //将普通节点Node转换成红黑树节点TreeNode,这个转换非常简单,
                //就是根据Node创建一个TreeNode对象,注意最后一个参数next为空
                TreeNode<K,V> p = replacementTreeNode(e, null);
                //如果为节点是空,那么说明链表是空的,刚刚创建的红黑树节点p就是头结点
                //注意,这里说的链表是空的,不是指待转换的链表,而是创建了一个新的链表
                if (tl == null)

                    //将头节点指向p
                    hd = p;
                else {

                    //如果尾节点不为空,那么将新建的红黑树节点插入链表尾部
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);

            //经过上面那一通折腾,原来的链表都变成一个全新的,连接红黑树节点的链
            //表;这里就将新的链表放入桶中;然后调用头结点的treeify进行真正的树化
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

    treeifyBin方法只是树化的一个热身步骤,最重要的是treeify方法:

        /**
         * Forms tree of the nodes linked from this node.
         * 这里才是真正的树化过程
         * @return root of tree
         */
        final void treeify(Node<K,V>[] tab) {

            //红黑树的根节点
            TreeNode<K,V> root = null;

            //遍历此链表,起点是调用treeify方法的节点,也就是链表的头结点
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                //给next赋值,代表的是当前遍历到的节点的后继节点
                next = (TreeNode<K,V>)x.next;
                //首先将当前的节点的左右子节点置空,只有当前节点的next节点才能确定左右子节点
                x.left = x.right = null;

                //如果根节点是空的话,首先要确定根节点
                if (root == null) {

                    //根节点是没有父节点的,所以将parent置空
                    x.parent = null;

                    //根节点必须是黑色的
                    x.red = false;

                    //将当前节点赋值给根节点
                    root = x;
                }

                //下面就是在根节点的下面插入子节点
                else {
                    //首先拿到链表节点的key
                    K k = x.key;

                    //拿到链表节点的hash值
                    int h = x.hash;

                    //key的类型信息
                    Class<?> kc = null;

                    //以根节点为起点遍历红黑树,确定链表节点应该插到红黑树的哪个地方
                    for (TreeNode<K,V> p = root;;) {

                        //dir是链表节点和红黑树中的节点的比较结果;ph是红黑树节点的hash值
                        int dir, ph;

                        //拿到红黑树节点的key值
                        K pk = p.key;

                        //如果红黑树节点的hash值比链表节点大,说明链表节点应该插在红黑树节点左边,dir == -1
                        if ((ph = p.hash) > h)
                            dir = -1;

                        //如果红黑树节点的hash值比链表节点大,说明链表节点应该插在红黑树节点左边,dir == -1
                        else if (ph < h)
                            dir = 1;

                        //如果链表节点和红黑树节点的hash值一样,但是两者有没有可比性,那么只能出
                        //绝招了,用tieBreakOrder比较两个对象的内存地址的hash;这个过程之前说过
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            dir = tieBreakOrder(k, pk);

                        //将红黑树节点赋值给xp,xp就会别当做父节点来使唤
                        TreeNode<K,V> xp = p;

                        //根据dir判断链表节点应该插在红黑树的左子节点还是右子节点
                        if ((p = (dir <= 0) ? p.left : p.right) == null) {
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x;
                            //插入后修复平衡,比较复杂,还没研究
                            root = balanceInsertion(root, x);
                            break;
                        }
                    }
                }
            }
            //确保链表头节点是红黑树的根节点
            moveRootToFront(tab, root);
        }

    可以看到,treeify本身不难,难的是修复红黑树平衡的方法balanceInsertion,这个方法没研究过,不敢妄言。
    自此,HashMap的put方法研究的差不多了。

上一篇下一篇

猜你喜欢

热点阅读