4、JDK1.8HashMap源码分析系列文章(split、untreeify、resize)

1、扩容过程中的红黑树元素转移方法-split

在这个方法中有一个重要的常数,反树化的阈值6,而树化的阈值是8,之所以不等,是防止单个元素增加删除造成的频繁树化和反树化的过程,反而降低效率。

private static final int UNTREEIFY_THRESHOLD = 6;
        /**
         * 红黑树元素转移
         *
         * @param map   当前Map
         * @param tab   扩容后的新数组
         * @param index 索引位置
         * @param bit   扩容前的容量
         * @return void
         * @Author muyi
         * @Date 16:24 2020/7/31
         */
        final void split(HashMap<K, V> map, HashMap.Node<K, V>[] tab, int index, int bit) {
            /**
             * loHead-低位红黑树头节点,loTail-低位红黑树尾节点,hiHead-高位红黑树头节点,hiTail-高位红黑树尾节点,next-后继节点,b-根节点
             * lc-低位红黑树节点的个数,hc-高位红黑树的节点个数
             * 因为oldCap是2的幂次方,所以e.hash & oldCap 这个计算结果要么是0,要么是1,
             * 当计算结果为1时,放在高位中,结果为0时,放在低位中
             */
            HashMap.TreeNode<K, V> b = this;
            HashMap.TreeNode<K, V> loHead = null, loTail = null;
            HashMap.TreeNode<K, V> hiHead = null, hiTail = null;
            int lc = 0, hc = 0;
            for (HashMap.TreeNode<K, V> e = b, next; e != null; e = next) {
                next = (HashMap.TreeNode<K, V>) e.next;
                e.next = null;
                // 这里虽然是红黑树节点,但是只是对双向链表进行了维护,未对红黑树进行任何操作
                if ((e.hash & bit) == 0) {
                    if ((e.prev = loTail) == null)
                        loHead = e;
                    else
                        loTail.next = e;
                    loTail = e;
                    ++lc;
                } else {
                    if ((e.prev = hiTail) == null)
                        hiHead = e;
                    else
                        hiTail.next = e;
                    hiTail = e;
                    ++hc;
                }
            }
            // 如果红黑树的个数小于UNTREEIFY_THRESHOLD(6)时,将红黑树转化成链表
            if (loHead != null) {
                if (lc <= UNTREEIFY_THRESHOLD)
                    // 转化成普通链表
                    tab[index] = loHead.untreeify(map);
                else {
                    /**
                     * 大于6的时候,将头节点放再数组的对应索引位置
                     * 如果高位链表不存在,说明树形结构未改变,整个元素转移的过程结束,
                     * 如果高位链表存在,说明原来的一颗红黑树被拆成了两棵红黑树,就需要重新树化
                     */
                    tab[index] = loHead;
                    if (hiHead != null)
                        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);
                }
            }
        }

2、反树化过程中将TreeNode转换成Node节点-untreeify

        /**
         * 将红黑树还原成链表
         *
         * @param map
         * @return com.example.bd.javaee.hashmap.HashMap.Node<K, V>
         * @Author muyi
         * @Date 16:33 2020/7/31
         */
        final HashMap.Node<K, V> untreeify(HashMap<K, V> map) {
            HashMap.Node<K, V> hd = null, tl = null;
            for (HashMap.Node<K, V> q = this; q != null; q = q.next) {
                // 将TreeNode转换成Node
                HashMap.Node<K, V> p = map.replacementNode(q, null);
                if (tl == null)
                    hd = p;
                else
                    tl.next = p;
                tl = p;
            }
            return hd;
        }

3、扩容方法-resize

        /**
         * 扩容阈值
         */
        int threshold;

        /**
         * 最大容量
         */
        private static final int MAXIMUM_CAPACITY = 1 << 30;

        /**
         * loadFactor译为装载因子。装载因子用来衡量HashMap满的程度。
         * loadFactor的默认值为0.75f。
         * 计算HashMap的实时装载因子的方法为:size/capacity,而不是占用桶的数量去除以capacity
         */
        final float loadFactor;

        /**
         * 负载系数,当数据量达到hashMap的容量与该值的乘积时,将会进行扩容操作
         */
        private static final float DEFAULT_LOAD_FACTOR = 0.75f;
 final HashMap.Node<K, V>[] resize() {
            HashMap.Node<K, V>[] oldTab = table;
            // 扩容前的容量
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            // 扩容前的扩容阈值
            int oldThr = threshold;
            // 新的阈值设置为0;
            int newCap, newThr = 0;
            // 原始容量大于0,进行扩容操作
            if (oldCap > 0) {
                // 如果已经扩容至最大值,则不再允许扩容,直接返回原table数组
                if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                    // 扩容一次性扩容2倍,如果扩容后的值位于 [默认初始容量16,最大值) 之间,则扩容成功
                } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                        oldCap >= DEFAULT_INITIAL_CAPACITY)
                    // 无论容量值是否修改成功,阈值都在原基础上扩大2倍。
                    newThr = oldThr << 1;
                // 如果原始容量 = 0(原始容量值不可能存在负数),并且阈值大于0,则直接设置新容量的值与原阈值相等
            } else if (oldThr > 0)
                // 注意这个分支,这里只对新的容量值进行了修改,但是新的阈值未进行修改,还是原始值0,这也就是后面判断(newThr == 0)的情况
                newCap = oldThr;
                // 如果阈值和原容量值均等于0,则直接初始化新阈值和容量等于默认值
            else {
                newCap = DEFAULT_INITIAL_CAPACITY;// newCap = 16;
                newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // newThr=(int)16 *0.75
            }

            if (newThr == 0) {
                // 计算一个新的阈值
                float ft = (float) newCap * loadFactor;
                /**
                 * 设时候如果同时满足newCap、ft均小于最大容量值,则说明阈值合法,否则阈值就设置为最大整数值。
                 * 个人理解:这里之所以做这样的判断,是因为在上面新的容量值直接设置成了原阈值,阈值是可以在实例化的时候指定的,
                 * 防止人为的失误,造成阈值或者容量超过最大容量
                 */
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
                        (int) ft : Integer.MAX_VALUE);
            }
            // 修改HashMap的阈值
            threshold = newThr;
            @SuppressWarnings({"rawtypes", "unchecked"})
            HashMap.Node<K, V>[] newTab = (HashMap.Node<K, V>[]) new HashMap.Node[newCap];
            table = newTab;
            // 当oldTab不为空的时候,将原数组中的元素移动到新的数组中
            if (oldTab != null) {
                for (int j = 0; j < oldCap; ++j) {
                    HashMap.Node<K, V> e;
                    if ((e = oldTab[j]) != null) {
                        // 原数组中的元素置空
                        oldTab[j] = null;
                        // 如果后继节点为null,说明当前位置只有一个元素,直接计算位置,将该值赋给新数组的对应位置即可
                        if (e.next == null)
                            newTab[e.hash & (newCap - 1)] = e;
                            // 如果该数据类型是红黑树类型节点
                        else if (e instanceof HashMap.TreeNode)
                            ((HashMap.TreeNode<K, V>) e).split(this, newTab, j, oldCap);
                        else {
                            /**
                             * 普通类型的节点(链表元素转移)
                             * 在对链表进行转移的时候,这里与1.7有一点点区别,1.7是一个一个的计算转移,这里是先计算再转移
                             * loHead-低位头节点,loTail-低位尾节点,hiHead-高位头节点,hiTail-高位尾节点,next-后继节点
                             * 因为oldCap是2的幂次方,所以e.hash & oldCap 这个计算结果要么是0,要么是1,
                             * 当计算结果为1时,放在高位链表中,结果为0时,放在低位链表中
                             */
                            HashMap.Node<K, V> loHead = null, loTail = null;
                            HashMap.Node<K, V> hiHead = null, hiTail = null;
                            HashMap.Node<K, V> next;
                            do {
                                next = e.next;
                                if ((e.hash & oldCap) == 0) {
                                    // 如果尾节点为空,说明当前链表为空,直接将当前节点置于头节点,否则置于尾节点之后
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    // 尾节点指向当前节点
                                    loTail = e;
                                } else {
                                    if (hiTail == null)
                                        hiHead = e;
                                    else
                                        hiTail.next = e;
                                    hiTail = e;
                                }
                            } while ((e = next) != null);
                            // 在执行了上面的操作后,如果尾节点不为空,说明尾节点指向了最后一个节点,所以需要将尾节点指向null,
                            if (loTail != null) {
                                loTail.next = null;
                                // 在数组的索引位置放入头节点
                                newTab[j] = loHead;
                            }
                            if (hiTail != null) {
                                hiTail.next = null;
                                // 高位链表放置的索引位置需要加原链表的长度,元素分散放置
                                newTab[j + oldCap] = hiHead;
                            }
                        }
                    }
                }
            }
            return newTab;
        }

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值