Hashmap源码解析-扩容函数

栏目: 数据库 · 发布时间: 5年前

内容简介:1.更新容量和阈值,​ if cap>0, 不超过上限的情况下cap、thre都乘2​ if cap=0, oldThr>0, 说明初始化的时候赋初始容量参数了,newCap=oldThr

1.更新容量和阈值,

​ if cap>0, 不超过上限的情况下cap、thre都乘2

​ if cap=0, oldThr>0, 说明初始化的时候赋初始容量参数了,newCap=oldThr

​ if cap=0, oldthr=0, 直接重新初始化,cap=16,thre=12

2.更新哈希桶, 遍历原桶

​ if 只有一个节点,直接挪过去

​ if 链表有超过8个节点,是红黑树, 复杂, 再说todo

​ if 少于8个的链表,则可能挪到低位,也可能挪到高位,看它本身hash在新容量时应在哪里,代码中巧妙通过与oldCap & 的方式判断需改到高还是低,具体在代码注释中有

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

    //oldTab 为当前表的哈希桶

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

    //当前哈希桶的容量 length

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

    //当前的阈值

    int oldThr = threshold;

    //初始化新的容量和阈值为0

    int newCap, newThr = 0;

    //如果当前容量大于0

    if (oldCap > 0) {

        //如果当前容量已经到达上限

        if (oldCap >= MAXIMUM_CAPACITY) {

            //则设置阈值是2的31次方-1

            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

        newCap = oldThr;//那么新表的容量就等于旧的阈值

    else {     //如果当前表是空的,而且也没有阈值。代表是初始化时没有任何容量/阈值参数的情况               // zero initial threshold signifies using defaults

        newCap = DEFAULT_INITIAL_CAPACITY;//此时新表的容量为默认的容量 16

        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//新的阈值为默认容量16 * 默认加载因子0.75f = 12

    }

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

            //取出当前的节点 e

            Node<K,V> e;

            //如果当前桶中有元素,则将链表赋值给e

            if ((e = oldTab[j]) != null) {

                //将原哈希桶置空以便GC

                oldTab[j] = null;

                //如果当前链表中就一个元素,(没有发生哈希碰撞)

                if (e.next == null)

                    //直接将这个元素放置在新的哈希桶里。

                    //注意这里取下标 是用 哈希值 与 桶的长度-1 。 由于桶的长度是2的n次方,这么做其实是等于 一个模运算。但是效率更高

                    newTab[e.hash & (newCap - 1)] = e;

                    //如果发生过哈希碰撞 ,而且是节点数超过8个,转化成了红黑树(暂且不谈 避免过于复杂, 后续专门研究一下红黑树)

                else if (e instanceof TreeNode)

                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);

                //如果发生过哈希碰撞,节点数小于8个。则要根据链表上每个节点的哈希值,依次放入新哈希桶对应下标位置。

                else { // preserve order

                    //因为扩容是容量翻倍,所以原链表上的每个节点,现在可能存放在原来的下标,即low位, 或者扩容后的下标,即high位。 high位=  low位+原哈希桶容量

                    //低位链表的头结点、尾节点

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

                    //高位链表的头节点、尾节点

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

                    Node<K,V> next;//临时节点 存放e的下一个节点

                    do {

                        next = e.next;

                        // 此处操作跟hash计算索引有关

                        // 在 HashMap 中,索引的计算方法为 (n - 1) & hash

                        // 所以,在进行扩容操作 (n*2) 后,该计算结果可能导致变更

                        // 例如

                        // 有一个值为 111001 的 hash

                        // 扩容前 n=16(10000)  n-1=15(1111)  (n - 1) & hash = 1111 & 111001= 001001

                        // 扩容后 n=32(100000) n-1=31(11111) (n - 1) & hash = 11111 & 111001= 011001

                        // 假如 hash 值为 101001

                        // 那么会发现扩容前  1111 & 101001 = 001001

                        //           扩容后 11111 & 101001 = 001001

                        // 所以可知,在进行扩容操作时,主要按照 hash 与 原数组长度中1的对应位置有关

                        // 如果 hash 中对应的位置为0,扩容后索引结果不变

                        // 不为0,表示索引结果为原结果+原数组长度

                        // 而 hash 中该对应位置的值只存在俩种可能 0,1

                        // 所以在该节点中的数据大约有一半索引不变,一半为原索引+原数组长度

                        // 通过 e.hash & oldCap 的方式可以得知 hash 在 oldCap 1对应的位置是否为0或1

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

                    //将低位链表存放在原index处,

                    if (loTail != null) {

                        loTail.next = null;

                        newTab[j] = loHead;

                    }

                    //将高位链表存放在新index处

                    if (hiTail != null) {

                        hiTail.next = null;

                        newTab[j + oldCap] = hiHead;

                    }

                }

            }

        }

    }

    return newTab;

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

首席产品官2 从白领到金领

首席产品官2 从白领到金领

车马 / 机械工业出版社 / 79元

《首席产品官》共2册,旨在为产品新人成长为产品行家,产品白领成长为产品金领,最后成长为首席产品官(CPO)提供产品认知、能力体系、成长方法三个维度的全方位指导。 作者在互联网领域从业近20年,是中国早期的互联网产品经理,曾是周鸿祎旗下“3721”的产品经理,担任CPO和CEO多年。作者将自己多年来的产品经验体系化,锤炼出了“产品人的能力杠铃模型”(简称“杠铃模型”),简洁、直观、兼容性好、实......一起来看看 《首席产品官2 从白领到金领》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具