0

0

hashmap的扩容机制是什么

青灯夜游

青灯夜游

发布时间:2023-03-15 15:39:36

|

4574人浏览过

|

来源于php中文网

原创

hashmap的扩容机制是:重新计算容量,用一个新的数组替换原来的数组。重新计算原数组的所有数据并插入一个新数组,然后指向新数组;如果数组在容量扩展前已达到最大值,则直接将阈值设置为最大整数返回。

hashmap的扩容机制是什么

本教程操作环境:windows7系统、java8、Dell G3电脑。

什么是扩容(resize)?

 扩容(resize):就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组,就像我们用一个小桶装水,如果想装更多的水,就得换大水桶。

什么时候扩容?

 当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值(threshold),即当前容器内的元素个数大于当前数组的长度乘以加载因子的值的时候,就要自动扩容了。

hashmap扩容原理

hashMap扩容就是重新计算容量,向hashMap不停的添加元素,当hashMap无法装载新的元素,对象将需要扩大数组容量,以便装入更多的元素。

1.jpg

HashMap容量扩展的特性,加载因子越大,空间利用率越高,扩容前需要填充的元素越多,put操作越快,但链表容易过长,hash碰撞概率大,get操作慢。加载因子越小,get操作越快,链表越短,hash碰撞概率低。但是,空间利用率低。put元素太多会导致频繁扩容,影响性能。

2.jpg

HashMap的容量扩展原理:Hashmap的方法是用新数组替换原数组,重新计算原数组中的所有数据,插入新数组,然后指向新数组;如果数组在扩容前已经达到最大,则直接将阈值设置为最大整数返回。

扩容的过程

 下面采用源代码+图片+文字描述的方式介绍HashMap的扩容过程。

/** 
 * HashMap 添加节点 
 * 
 * @param hash        当前key生成的hashcode 
 * @param key         要添加到 HashMap 的key 
 * @param value       要添加到 HashMap 的value 
 * @param bucketIndex 桶,也就是这个要添加 HashMap 里的这个数据对应到数组的位置下标 
 */  
void addEntry(int hash, K key, V value, int bucketIndex) {  
    //数组扩容条件:1.已经存在的key-value mappings的个数大于等于阈值  
    //             2.底层数组的bucketIndex坐标处不等于null  
    if ((size >= threshold) && (null != table[bucketIndex])) {  
        resize(2 * table.length);//扩容之后,数组长度变了  
        hash = (null != key) ? hash(key) : 0;//为什么要再次计算一下hash值呢?  
        bucketIndex = indexFor(hash, table.length);//扩容之后,数组长度变了,在数组的下标跟数组长度有关,得重算。  
    }  
    createEntry(hash, key, value, bucketIndex);  
}  
  
/** 
 * 这地方就是链表出现的地方,有2种情况 
 * 1,原来的桶bucketIndex处是没值的,那么就不会有链表出来啦 
 * 2,原来这地方有值,那么根据Entry的构造函数,把新传进来的key-value mapping放在数组上,原来的就挂在这个新来的next属性上了 
 */  
void createEntry(int hash, K key, V value, int bucketIndex) {  
    HashMap.Entry e = table[bucketIndex];  
    table[bucketIndex] = new HashMap.Entry<>(hash, key, value, e);  
    size++;  
}

 上述addEntry方法中,如果size(当前容器内的元素个数)大于等于threshold(数组长度乘以负载因子),并且底层数组的bucketIndex坐标处不等于null,那么将会进行扩容(resize)。否则,不会进行扩容。

 下面将着重介绍一下扩容的过程:

        void resize(int newCapacity) {   //传入新的容量
            Entry[] oldTable = table;    //引用扩容前的Entry数组
            int oldCapacity = oldTable.length;
            if (oldCapacity == MAXIMUM_CAPACITY) {  //扩容前的数组大小如果已经达到最大(2^30)了
                threshold = Integer.MAX_VALUE; //修改阈值为int的最大值(2^31-1),这样以后就不会扩容了
                return;
            }
     
            Entry[] newTable = new Entry[newCapacity];  //初始化一个新的Entry数组
            transfer(newTable);	此行有遗漏,勘误见下面引用	//!!将数据转移到新的Entry数组里
            table = newTable;                           //HashMap的table属性引用新的Entry数组
            threshold = (int) (newCapacity * loadFactor);此行有遗漏,勘误见下面引用//修改阈值
        }

由wenni328博友修正:transfer(newTable); ==> transfer(newTable, initHashSeedAsNeeded(newCapacity));
threshold = (int) (newCapacity * loadFactor); ==> threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);

 扩容前首先获取扩容前数组的引用地址存进oldTable变量中,然后判断扩容前数组长度是否达到了int类型存储的最大值,如果是则放弃此次扩容,因为数组容量已经达到最大,无法再扩容了。

 下图为程序执行完Entry[] newTable = new Entry[newCapacity];代码之后的状态:

3.png

 这里就是使用一个容量更大的数组来代替已有的容量小的数组,transfer()方法将原有Entry数组的元素拷贝到新的Entry数组里。

        void transfer(Entry[] newTable) {
            Entry[] src = table;                   //src引用了旧的Entry数组
            int newCapacity = newTable.length;
            for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组
                Entry e = src[j];             //取得旧Entry数组的每个元素
                if (e != null) {
                    src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)
                    do {
                        Entry next = e.next;
                        int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置
                        e.next = newTable[i]; //标记[1]
                        newTable[i] = e;      //将元素放在数组上
                        e = next;             //访问下一个Entry链上的元素
                    } while (e != null);
                }
            }
        }

        static int indexFor(int h, int length) {
            return h & (length - 1);
        }

 newTable[i]的引用赋给了e.next,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置;这样先放在一个索引上的元素终会被放到Entry链的尾部(如果发生了hash冲突的话)。在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。

 下面会以图片的形式演示一下transfer的过程(下面图片的红色字体表示与上图有区别的地方,后面图片都是这样,后面红色字体说明不再赘述)

好买卖商城
好买卖商城

好买卖商城开源商城 是基于Opencart网店系统,针对中文用户而改进的本地化分支,是真正的开源PHP中文网店系统,兼容Opencart的插件。该系统具有易于操作的可视化安装界面、完善的前台商品展示和户在线购物车功能、强大的后台管理和维护功能模块简单易用,灵活的插件机制,更易于扩展。另外,好买卖商城开源商城 还集成集成了支付宝等支付和物流插件,更适合中文用户使用。 好买卖商城2.0开源商城流程进行

下载

 下图为程序执行完src[j] = null;代码之后的状态(这是第一次循环时的状态):

4.png

 首先,将table[]数组的引用地址赋值给src[]数组。

 然后,Entry e = src[j];是将src[j]位置的链表交给e变量存储。由于src[j]位置的链表已经交给e存储了,所以可以大胆的将src[j]=null;然后等待垃圾回收。

 下图为程序执行完Entry next = e.next;代码之后的状态(这是第一次循环时的状态):

5.png

 这里先将e.next的值备份至next变量中,后续代码会将e.next的指向更改,所以在这里将e.next的值备份。

 下图为程序执行完e.next = newTable[i];代码之后的状态(这是第一次循环时的状态):

6.png

 由于newTable[3]的值为null,所以e.next为null,如上图所示。

 下图为程序执行完newTable[i] = e;代码之后的状态(这是第一次循环时的状态):

7.png

 下图为程序执行完e = next;代码之后的状态(这是第一次循环时的状态):

8.png

 如上述所示,Entry1这个节点成功插入到了newTable中,一轮循环结束时,因为判断e!=null,所以会再重复上述过程,直至所有节点移动到newTable中。

小结

  • 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。
  • 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。
  • HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap。
  • JDK1.8引入红黑树大程度优化了HashMap的性能。

更多编程相关知识,请访问:编程教学!!

相关文章

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
云朵浏览器入口合集
云朵浏览器入口合集

本专题整合了云朵浏览器入口合集,阅读专题下面的文章了解更多详细地址。

0

2026.01.20

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

20

2026.01.20

PS使用蒙版相关教程
PS使用蒙版相关教程

本专题整合了ps使用蒙版相关教程,阅读专题下面的文章了解更多详细内容。

62

2026.01.19

java用途介绍
java用途介绍

本专题整合了java用途功能相关介绍,阅读专题下面的文章了解更多详细内容。

87

2026.01.19

java输出数组相关教程
java输出数组相关教程

本专题整合了java输出数组相关教程,阅读专题下面的文章了解更多详细内容。

39

2026.01.19

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

10

2026.01.19

xml格式相关教程
xml格式相关教程

本专题整合了xml格式相关教程汇总,阅读专题下面的文章了解更多详细内容。

13

2026.01.19

PHP WebSocket 实时通信开发
PHP WebSocket 实时通信开发

本专题系统讲解 PHP 在实时通信与长连接场景中的应用实践,涵盖 WebSocket 协议原理、服务端连接管理、消息推送机制、心跳检测、断线重连以及与前端的实时交互实现。通过聊天系统、实时通知等案例,帮助开发者掌握 使用 PHP 构建实时通信与推送服务的完整开发流程,适用于即时消息与高互动性应用场景。

19

2026.01.19

微信聊天记录删除恢复导出教程汇总
微信聊天记录删除恢复导出教程汇总

本专题整合了微信聊天记录相关教程大全,阅读专题下面的文章了解更多详细内容。

160

2026.01.18

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.7万人学习

C# 教程
C# 教程

共94课时 | 7.2万人学习

Java 教程
Java 教程

共578课时 | 48.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号