【HashMap扩容机制】

关于hashMap扩容 例子1: HashMap<Integer, Integer> map = new HashMap<>(16, 2); for (int i = 0; i < 40; i++) { map.put(i, i); } 描述:创建map对象长度是16,阈值为32的map对象。阈值的计算16*2=32,阈值的作用是,, 当向map对象添加超过32条数据时,也就是当添加到底33条数据时,要进行扩容。如果不设置默认加 阅读详情

我是廖志伟,一名Java开发工程师、幕后大佬社区创始人、Java领域优质创作者、优快云博客专家。拥有多年一线研发经验,研究过各种常见框架及中间件的底层源码,对于大型分布式、微服务、三高架构(高性能、高并发、高可用)有过实践架构经验。

博主:java_wxid
社区:幕后大佬


文章目录


本文的大概内容:

HashMap扩容机制


将(k1,v1)直接放入Node类型的数组中,这个数组初始化容量是16,默认的加载因子是0.75。

HashMap有两个参数影响其性能:初始容量和加载因子。
容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。
加载因子其实是用来判断当前HashMap<K,V>中存放的数据量。
当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行扩容、rehash操作(即重建内部数据结构),扩容后的哈希表将具有两倍的原容量。hashmap初始化容量时候,对容量大小做的处理,保证初始化容量为最近的2的幂次方。

HashMap初始化容量非得是2的幂次方,2的倍数不行么,奇数不行么?

  • 2的幂次方:hashmap在确定元素落在数组的位置的时候,计算方法是(n - 1) & hash,n为数组长度也就是初始容量 。hashmap结构是数组,每个数组里面的结构是node(链表或红黑树),正常情况下,如果你想放数据到不同的位置,肯定会想到取余数确定放在那个数组里,计算公式:hash % n,这个是十进制计算。在计算机中, (n - 1) & hash,当n为2次幂时,会满足一个公式:(n - 1) & hash = hash % n,计算更加高效。
  • 奇数不行:在计算hash的时候,确定落在数组的位置的时候,计算方法是(n - 1) & hash,奇数n-1为偶数,偶数2进制的结尾都是0,经过hash值&运算后末尾都是0,那么0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这样就会造成空间的浪费而且会增加hash冲突。

HashMap加载因子为什么是0.75?

如果加载因子比较大,扩容发生的频率比较低,浪费的空间比较小,发生hash冲突的几率比较大。比如,加载因子是1的时候,hashmap长度为128,实际存储元素的数量在64至128之间时间段比较多,这个时间段发生hash冲突比较多,造成数组中其中一条链表比较长,会影响性能。

如果加载因子比较小,扩容发生的频率比较高,浪费的空间比较多,发生hash冲突的几率比较小。比如,加载因子是0.5的时候,hashmap长度为128,当数量达到65的时候会触发扩容,扩容后为原理的256,256里面只存储了65个浪费了。

综合了一下,取了一个平均数0.75作为加载因子。当负载因子为0.75,时代入到泊松分布公式,计算出来长度为8时,概率=0.00000006,概率很小了,链表长度为8时转红黑树。

可能引发的问题: HashMap实际使用过程中会出现一些性能问题以及线程安全问题。
hashmap
在JDK1.7中有二块值得注意的地方。

  • 扩容的时候需要rehash操作,需要将所有的数据重新计算HashCode,然后赋给新的HashMap<K,V>,rehash的过程是非常耗费时间和空间的。
  • 当并发执行扩容操作时会造成环形链和数据丢失的情况,开多个线程不断进行put操作,所以当旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置(就是因为头插),所以最后的结果打乱了插入的顺序,就可能发生环形链和数据丢失的问题,引起死循环,导致CPU利用率接近100%。

hashmap
在JDK1.8中,对HashMap进行了优化。

  • 经过rehash之后元素的位置,要么是在原位置,要么是在原位置再移动2次幂的位置。HashMap的数组长度恒定为2的n次方,也就是说只会为16,32,64,128这种数。即便你给的初始值是13,最后数组长度也会变成16,它会取与你传入的数最近的一个2的n次方的数。
    hashmap

扩容之后元素的位置是否改变,完全取决于紫色框框中的运算是为0还是1,为0则新位置与原位置相同,不需要换位置,不为零则需要换位置。

在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成原位置+原数组长度。

为什么新的位置是原位置+原数组长度?

HashMap中运算数组的位置使用的是leng-1,对于初始长度为16的数组,扩容之后为32,对应的leng-1就是15,31。

举例一:假设某个元素的hashcode为52,这个52与15运算做按位与运算的的结果是4,这个52与31做按位与运算的的结果是20,20不就是等于4+16吗,刚好是原数组的下标+原数组的长度。
举例二:假设某个元素的hashcode为100,100&15=4,100&31=4,对于HashCode为100的元素来说,扩容后与扩容前其所在数组中的下标均为4。

以上两个例子证明了,经过rehash之后,元素的位置要么是在原位置,要么是在原位置加原数组长度的位置。

因为每次扩容会把原数组的长度*2,那么再二进制上的表现就是多出来一个1

比如原数组16-1二进制为0000 1111
那么扩容后的32-1的二进制就变成了0001 1111
再次扩容64-1就是0011 1111

扩容之后元素的位置是否改变则取决于与这个多出来的1的运算结果,运算结果为0则不需要换位置,运算结果为1则换新位置,新位置为老位置的高位进1。

既省去了重新计算hash值的时间,而且由于新增的1bit是0还是1,可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。

  • 发生hash碰撞,不再采用头插法方式,而是直接插入链表尾部,因此不会出现环形链表的情况,但是在多线程环境下,会发生数据覆盖的情况,如果没有hash碰撞的时候,它会直接插入元素。如果线程A和线程B同时进行put操作,刚好这两条不同的数据hash值一样,并且该位置数据为null,线程A进入后还未进行数据插入时挂起,而线程B正常执行,从而正常插入数据,然后线程A获取CPU时间片,此时线程A不用再进行hash判断了,线程A会把线程B插入的数据给覆盖,导致数据发生覆盖的情况,发生线程不安全。可参考【HashMap并发修改异常】

总结

以上就是今天要讲的内容,还希望各位读者大大能够在评论区积极参与讨论,给文章提出一些宝贵的意见或者建议,合理的内容,我会采纳更新博文,重新分享给大家。


提示:以下都是资源分享,求个一键三连。

博客封面

首先我要说声抱歉,作为一个学习的平台,封面引人注目是营销策略,大家不用太过在意哈,专注博客内容本身即可。当然有同学惦记着我博客的封面,这里也分享出来给大家。
点击:博客封面
提取码:2021

面试资料

福利大放送,我就求个一键三连,拜托了,这对我真的很重要。
点击:面试资料
提取码:2021

200套PPT模板

福利大放送,我就求个一键三连,拜托了,这对我真的很重要。
点击:200套PPT模板
提取码:2021

提问的智慧

福利大放送,我就求个一键三连,拜托了,这对我真的很重要。
点击:提问的智慧
提取码:2021

一键三连

感谢大家的支持,用心写博文分享给大家,你的支持(点赞、收藏、关注)是对我创作的最大帮助。
微信公众号:南北踏尘
主页地址:java_wxid
社区地址:幕后大佬

给读者大大的话

我本身是一个很普通的程序员,放在人堆里,除了与生俱来的盛世美颜、所剩不多的发量,就剩下180的大高个了。就是我这样的一个人,默默坚持写博文也有好多年了,有句老话说的好,牛逼之前都是傻逼式的坚持。希望自己可以通过大量的作品,时间的积累,个人魅力、运气和时机,可以打造属于自己的技术影响力。同时也希望自己可以成为一个懂技术,懂业务,懂管理的综合型人才,作为项目架构路线的总设计师,掌控全局的团队大脑,技术团队中的绝对核心是我未来几年不断前进的目标。

点击关注博主

点击关注博主:幕后大佬
点击关注博主:java_wxid
点击关注社区:幕后大佬

HashMap并发扩容死循环问题 HashMap没有使用考虑并发问题,必然是线程不安全的。 HashMap1.7每一个桶使用的是单向链表实现。而扩容采取的是头插法。 1.什么是头插法? 链表反转的迭代写法就是经典的头插法 例如链表:1——>2 设反转后为:reverse=null; 那么依次取链表的节点插入反转后的头部,并把当前节点赋给resvese就是头插法。 下面是简要代码: class ListNode { ... public ListNode reverseNode(ListNode root){ ListNod 阅读详情

相关推荐

并发编程学习之ConcurrentHashMap扩容机制

一、简介 在ConcurrentHashMap中,比较复杂部分就是其扩容机制,因为涉及到多个线程分工合作完成数据迁移和key的rehash操作。 二、扩容思路 ConcurrentHashMap扩容一般分为两个步骤: 【a】Node<K,V>[] table数组的扩容,一般是扩大到原来数组大小的两倍; 【b】key的rehash以及数据的迁移:因为计算key在桶中的索引ind...

weixiaohuai的博客 2539

关于HashMap扩容机制

HashMap的底层有数组 + 链表(红黑树)组成,数组的大小可以在构造方法时设置,默认大小为16,数组中每一个元素就是一个链表,jdk7之前链表中的元素采用头插法插入元素,jdk8之后采用尾插法插入元素,由于插入的元素越来越多,查找效率就变低了,所以满足某种条件时,链表会转换成红黑树。随着元素的增加,HashMap的数组会频繁扩容,如果构造时不赋予加载因子默认值,那么负载因子默认值为0.75,数组扩容的情况如下: 1:当添加某个元素后,数组的总的添加元素数大于了 数组长度 * 0.75(默认,也可自己设

zhuzhianing的博客 5万+

HashMap扩容机制

HashMap扩容的核心是“扩容触发→容量计算→元素迁移”,JDK1.7到1.8的升级,本质是扩容效率和并发安全性的优化:JDK1.7:先扩容后插入,逐个元素重算下标、头插法迁移,低效且有死循环风险,无红黑树适配;JDK1.8:先插入后扩容,高低位整体迁移(核心优化),尾插法避免倒置,支持红黑树拆分与退化,高效且无死循环;核心记忆点:JDK1.8扩容关键是拆分高低位,低位留原下标,高位加旧容量,结合源码理解更透彻。

ysct_999的博客 4084

HashMap的扩容机制

HashMap在底层数据结构上采用了数组+链表+红黑树,通过散列映射来存储键值对数据因为在查询上使用散列码(通过键生成一个数字作为数组下标,这个数字就是hash code)所以在查询上的访问速度比较快,HashMap最多允许一对键值对的Key为Null,允许多对键值对的value为Null。它是非线程安全的。在排序上面是无序的。

wming666的博客 5561

24位ADC的精度陷阱:为什么你的ADS1232实测性能不达标?

本文深入探讨了24位ADC芯片ADS1232在实际应用中性能不达标的原因,揭示了高精度ADC性能依赖周边电路设计的关键问题。通过分析电源系统、参考电压稳定性、PCB布局等常见陷阱,提供了优化方案和调试技巧,帮助工程师充分发挥ADS1232的潜力,实现接近理论值的测量精度。

weixin_28285943的博客 405

E-Prime脑电实验打mark全攻略:从端口查询到串并口实战(附常见问题解决)

本文提供了E-Prime脑电实验打mark的完整实战指南,涵盖端口查询、并口与串口配置的核心步骤及常见问题解决。文章深入解析了mark原理,并详细介绍了通过E-Basic脚本和Serial设备对象发送信号的两种方法,帮助研究者实现E-Prime与脑电设备的高精度时间同步,确保实验数据可靠性。

4k5l6j7h8的博客 354

HashMap 的扩容机制

HashMap 的扩容机制无疑是保障其高性能表现的核心要素之一。通过在恰当的时机进行扩容操作,HashMap 能够有效地控制哈希冲突发生的概率,进而始终保持高效的操作效率。对于我们这些在日常开发工作中频繁使用 HashMap 的开发者来说,深入透彻地理解 HashMap 的扩容机制具有极为重要的意义,它能够帮助我们更加合理、高效地运用 HashMap 来解决各种实际的编程问题,避免因对其内部机制的不了解而导致的性能瓶颈或错误使用。

weixin_73687229的博客 1286

实战对比:VINS和ORBSLAM3在无人机避障中的表现(含实测数据)

本文通过实战测试,深度对比了VINS和ORBSLAM3两种主流视觉SLAM算法在无人机避障中的表现。测试聚焦于光照变化、动态遮挡和快速运动三大挑战,提供了详细的实测数据与性能分析。结果表明,VINS在计算效率和实时性上占优,而ORBSLAM3在定位精度和地图管理方面更稳健,为不同应用场景下的工程选型提供了清晰依据。

醉三国 627

HashMap扩容机制以及尾插法

1. resize定义 当HashMap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,所以这是一个通用的操作,很多人对它的性能表示过怀疑,不过想想我们的“均摊”原理,就释然了,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其...

傅红雪的专栏 2万+

树莓派4B网线直连Windows全流程:从IP查询到SSH登录避坑指南

本文提供了一份详细的树莓派4B通过网线直连Windows电脑的实战指南。核心步骤包括配置Windows网络共享、查询树莓派IP地址以及通过SSH进行安全登录与初始配置。文章深入解析了直连的网络原理,并重点分享了使用CMD命令和工具定位IP、避免常见错误的避坑技巧,帮助用户在没有显示器或路由器的环境下快速完成树莓派初始化。

grape的博客 1080

ROS——无人机ROS仿真包 rotors_simulator 编译教程

文章目录 安装wstool sudo apt install python3-wstool 创建ROS工程 $ mkdir -p ~/UAV/src $ cd ~/UAV/src $ catkin_init_workspace # initialize your catkin workspace $ wstool init 下载源码 git clone git@github.com:ethz-asl/rotors_simulator.git 源码链接: https://github.com/eth

漫长IT路 5863

HashMap的扩容机制原理

扩容的目的是在哈希表中的元素数量增加时,减少哈希冲突,维持较低的负载因子,从而保证查找、插入和删除操作的时间复杂度接近 O(1)。由于扩容是触发式的,且容量翻倍,分摊到每次插入操作的时间复杂度为 O(1)。遍历原数组中的每个桶,将每个键值对重新计算哈希值,并放入新数组的对应桶中。扩容的时间复杂度为 O(n),其中 n 是 HashMap 中的元素数量。扩容时,HashMap 会创建一个新的数组,容量为原数组的 2 倍。,表示当哈希表中的元素数量达到容量的 75% 时,触发扩容。

完全体 1454

解决RabbitMq登录时报出Not management user(Login failed)错误以及rabbitmqctl(RabbitMq)新增用户和用户角色权限

解决RabbitMq登录时报出Not management user(Login failed)错误以及rabbitmqctl(RabbitMq)查看用户权限、新增用户和用户角色权限。

念兮为美 7479

面试题:HashMap扩容机制

扩容机制 1.什么时候才需要扩容 在首次调用put方法的时候,初始化数组table 当HashMap中的元素个数超过数组大小(数组长度)*loadFactor(负载因子)时,就会进行数组扩容,loadFactor的默认值(DEFAULT_LOAD_FACTOR)是0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中的元素个数超过16×0.75...

qq_29860591的博客 1万+

【S32K3 MCAL配置】-4.1-CAN Driver:如何解决CAN帧发送丢帧问题

【S32K3 MCAL配置】-4.1-CAN Driver:如何解决CAN帧发送丢帧问题

「汽车电子助手」的博客 1440

SXU—软件测试方法和技术-朱少民-第三部分 软件测试方法大题汇总-附考点和相关习题及解答

软件测试方法和技术-朱少民-第三部分 软件测试方法—-最少测试用例个数,强一般弱一般强健壮、边界值分析、ns流程图、白盒逻辑覆盖代码、等价类划分,逻辑组合(条件)覆盖,计算圈复杂度等—附考点和相关习题及解答

weixin_63597914的博客 2211

HashMap的初始容量机制及扩容机制

通常在我们的应用中,HashMap是用到最多的数据结构之一,在JDK1.8之前,它的底层结构是数组+链表,而在JDK1.8之后,为了查询效率的优化(主要是当哈希碰撞较多的时候),它的底层结构变成了数组+链表+红黑树。今天就来探讨一下HashMap的扩容机制,这也是面试时被问到最多的问题。首先看一下源码中HashMap的四种构造方法。 从源码中可以看出:HashMap提供四种构...

Apeopl的博客 1万+

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Java 中 HashMap 的扩容机制是怎样的?

通过源码分析和实际场景结合,能够更精准地优化HashMap的使用,提升系统性能。判断元素是否留在原位置或迁移到新位置(原索引 + oldCap)。例如:容量为16时,阈值=12,当元素数量超过12时触发扩容。当链表长度≥8且当前数组长度<64时,优先扩容而非树化。,平衡了哈希碰撞概率与内存利用率。HashMap的扩容机制通过。

2302_81141145的博客 1802

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

说说HashMap的扩容机制

首先先看下HashMap几个重要的常量 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 默认初始化值16 在创建HashMap时,如果没有指定具体的值,初始化默认是16 static final int MAXIMUM_CAPACITY = 1 << 30 最大容量,即HashMap能创建的最大容量 static final float DEFAULT_LOAD_FACTOR = 0.75f; 负载因子,HashMap的

qq_37376024的博客 4366

HashMap 的扩容机制是怎样的?

作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。

smart_an的专栏 908

ArrayList和HashMap的扩容机制

本文详细分析了Java集合框架中ArrayList和HashMap的扩容机制。ArrayList采用动态数组实现,初始容量在JDK8后改为懒加载模式,首次添加元素时初始化为10。扩容时按照1.5倍增长,使用Arrays.copyOf高效复制数据。HashMap基于哈希表实现,当元素数量超过容量与负载因子(默认0.75)乘积时触发扩容,新容量为旧容量的2倍。扩容过程通过重新哈希将元素分配到新数组,优化了链表/红黑树的拆分效率。两者扩容都涉及数组复制和元素重新分配,会影响性能,且HashMap在多线程环境下扩容

2201_75642742的博客 770

图解HashMap扩容和ArrayList的扩容机制。

前言 众所周知,hashmap和Arraylist作为java中非常重要的一种数据结构,应用场景非常广泛,这篇文章主要针对HashMap和ArrayList的扩容机制进行分析。 HashMap扩容机制分析 在说HashMap扩容机制之前,有必要简述下HashMap的基本结构。以便各位更加清除的理解HashMap的底层是如何扩容的。HashMap自JDK1.8之后结构采用数组+单链表【单链表长度达到...

Smarts 6618
上一篇: 【HashMap底层数据结构】
下一篇: 【HashMap并发修改异常】
Java程序员廖志伟
Java程序员廖志伟 优质创作者: Java技术领域 优质创作者: Java技术领域 领域专家: 后端开发技术领域 领域专家: 后端开发技术领域
博客等级 码龄8年 2万+粉丝 1902原创
评论 18
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java程序员廖志伟

赏我包辣条呗

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值