HashMap 扩容方法

之前在高并发下 的HashMap遇到过的问题,第一个是元素多次插入HashMap达到一定饱和度时,随着HashMap容量增加Key映射位置容易发生冲突,第二种就是在多线程下出现死循环。扩容有两步:创建一个新的Entry空数组,长度是原数组的2倍;遍历原Entry数组,把所有的Entry重新Hash到新数组。为什么要重新Hash呢?因为长度扩大以后,Hash的规则也随之改变。下面是扩容实现。在多线程下使用ConcurrentHashMap,ConcurrentHashMap是线程安全的。

1、final Node<K, V>[] resize() {

    // 旧数组

    Node<K, V>[] oldTab = table;

    // 旧容量

    int oldCap = (oldTab == null) ? 0 : oldTab.length;

    // 旧扩容门槛

    int oldThr = threshold;

    int newCap, newThr = 0;

    if (oldCap > 0) {

        if (oldCap >= MAXIMUM_CAPACITY) {

            // 如果旧容量达到了最大容量,则不再进行扩容

            threshold = Integer.MAX_VALUE;

            return oldTab;

        } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&

                oldCap >= DEFAULT_INITIAL_CAPACITY)

            // 如果旧容量的两倍小于最大容量并且旧容量大于默认初始容量(16),则容量扩大为两部,扩容门槛也扩大为两倍

            newThr = oldThr << 1; // double threshold

    } else if (oldThr > 0) // initial capacity was placed in threshold

        // 使用非默认构造方法创建的map,第一次插入元素会走到这里

        // 如果旧容量为0且旧扩容门槛大于0,则把新容量赋值为旧门槛

        newCap = oldThr;

    else {              // zero initial threshold signifies using defaults

        // 调用默认构造方法创建的map,第一次插入元素会走到这里

        // 如果旧容量旧扩容门槛都是0,说明还未初始化过,则初始化容量为默认容量,扩容门槛为默认容量*默认装载因子

        newCap = DEFAULT_INITIAL_CAPACITY;

        newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

    }

    if (newThr == 0) {

        // 如果新扩容门槛为0,则计算为容量*装载因子,但不能超过最大容量

        float ft = (float) newCap * loadFactor;

        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?

                (int) ft : Integer.MAX_VALUE);

    }

    // 赋值扩容门槛为新门槛

    threshold = newThr;

    // 新建一个新容量的数组

    @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) {

                // 清空旧桶,便于GC回收 

                oldTab[j] = null;

                // 如果这个桶中只有一个元素,则计算它在新桶中的位置并把它搬移到新桶中

                // 因为每次都扩容两倍,所以这里的第一个元素搬移到新桶的时候新桶肯定还没有元素

                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

                    // 如果这个链表不止一个元素且不是一颗树

                    // 则分化成两个链表插入到新的桶中去

                    // 比如,假如原来容量为4,3、7、11、15这四个元素都在三号桶中

                    // 现在扩容到8,则3和11还是在三号桶,7和15要搬移到七号桶中去

                    // 也就是分化成了两个链表

                    Node<K, V> loHead = null, loTail = null;

                    Node<K, V> hiHead = null, hiTail = null;

                    Node<K, V> next;

                    do {

                        next = e.next;

                        // (e.hash & oldCap) == 0的元素放在低位链表中

                        // 比如,3 & 4 == 0

                        if ((e.hash & oldCap) == 0) {

                            if (loTail == null)

                                loHead = e;

                            else

                                loTail.next = e;

                            loTail = e;

                        } else {

                            // (e.hash & oldCap) != 0的元素放在高位链表中

                            // 比如,7 & 4 != 0

                            if (hiTail == null)

                                hiHead = e;

                            else

                                hiTail.next = e;

                            hiTail = e;

                        }

                    } while ((e = next) != null);

                    // 遍历完成分化成两个链表了

                    // 低位链表在新桶中的位置与旧桶一样(即3和11还在三号桶中)

                    if (loTail != null) {

                        loTail.next = null;

                        newTab[j] = loHead;

                    }

                    // 高位链表在新桶中的位置正好是原来的位置加上旧容量(即7和15搬移到七号桶了)

                    if (hiTail != null) {

                        hiTail.next = null;

                        newTab[j + oldCap] = hiHead;

                    }

                }

            }

        }

    }

    return newTab;

}


1、如果使用是默认构造方法,则第一次插入元素时初始化为默认值,容量为16,扩容门槛为12;

2、如果使用的是非默认构造方法,则第一次插入元素时初始化容量等于扩容门槛,扩容门槛在构造方法里等于传入容量向上最近的2的n次方;

3、如果旧容量大于0,则新容量等于旧容量的2倍,但不超过最大容量2的30次方,新扩容门槛为旧扩容门槛的2倍;

4、创建一个新容量的桶;

5、搬移元素,原链表分化成两个链表,低位链表存储在原来桶的位置,高位链表搬移到原来桶的位置加旧容量的位置;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352

推荐阅读更多精彩内容