专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

面试记录HashMap核心知识,扰动函数负载因子扩容链表拆分

  作者:小傅哥
  博客:https:bugstack。cn
  沉淀、分享、成长,让自己和他人都能有所收获!一、前言
  得益于DougLea老爷子的操刀,让HashMap成为使用和面试最频繁的API,没办法设计的太优秀了!
  HashMap最早出现在JDK1。2中,底层基于散列算法实现。HashMap允许null键和null值,在计算哈键的哈希值时,null键哈希值为0。HashMap并不保证键值对的顺序,这意味着在进行某些操作后,键值对的顺序可能会发生变化。另外,需要注意的是,HashMap是非线程安全类,在多线程环境下可能会存在问题。
  HashMap最早在JDK1。2中就出现了,底层是基于散列算法实现,随着几代的优化更新到目前为止它的源码部分已经比较复杂,涉及的知识点也非常多,在JDK1。8中包括;1、散列表实现、2、扰动函数、3、初始化容量、4、负载因子、5、扩容元素拆分、6、链表树化、7、红黑树、8、插入、9、查找、10、删除、11、遍历、12、分段锁等等,因涉及的知识点较多所以需要分开讲解,本章节我们会先把目光放在前五项上,也就是关于数据结构的使用上。
  数据结构相关往往与数学离不开,学习过程中建议下载相应源码进行实验验证,可能这个过程有点烧脑,但学会后不用死记硬背就可以理解这部分知识。二、资源下载
  本章节涉及的源码和资源在工程,interview04中,包括;10万单词测试数据,在doc文件夹扰动函数excel展现,在doc文件夹测试源码部分在interview04工程中
  可以通过关注公众号:bugstack虫洞栈,回复下载进行获取{回复下载后打开获得的链接,找到编号ID:19}三、源码分析1。写一个最简单的HashMap
  学习HashMap前,最好的方式是先了解这是一种怎么样的数据结构来存放数据。而HashMap经过多个版本的迭代后,乍一看代码还是很复杂的。就像你原来只穿个裤衩,现在还有秋裤和风衣。所以我们先来看看最根本的HashMap是什么样,也就是只穿裤衩是什么效果,之后再去分析它的源码。
  问题:假设我们有一组7个字符串,需要存放到数组中,但要求在获取每个元素的时候时间复杂度是O(1)。也就是说你不能通过循环遍历的方式进行获取,而是要定位到数组ID直接获取相应的元素。
  方案:如果说我们需要通过ID从数组中获取元素,那么就需要把每个字符串都计算出一个在数组中的位置ID。字符串获取ID你能想到什么方式?一个字符串最直接的获取跟数字相关的信息就是HashCode,可HashCode的取值范围太大了〔2147483648,2147483647〕,不可能直接使用。那么就需要使用HashCode与数组长度做与运算,得到一个可以在数组中出现的位置。如果说有两个元素得到同样的ID,那么这个数组ID下就存放两个字符串。
  以上呢其实就是我们要把字符串散列到数组中的一个基本思路,接下来我们就把这个思路用代码实现出来。1。1代码实现初始化一组字符串ListStringlistnewArrayList();list。add(jlkk);list。add(lopi);list。add(小傅哥);list。add(e4we);list。add(alpo);list。add(yhjk);list。add(plop);定义要存放的数组String〔〕tabnewString〔8〕;循环存放for(Stringkey:list){intidxkey。hashCode()(tab。length1);计算索引位置System。out。println(String。format(key值sIdxd,key,idx));if(nulltab〔idx〕){tab〔idx〕key;continue;}tab〔idx〕tab〔idx〕key;}输出测试结果System。out。println(JSON。toJSONString(tab));
  这段代码整体看起来也是非常简单,并没有什么复杂度,主要包括以下内容;初始化一组字符串集合,这里初始化了7个。定义一个数组用于存放字符串,注意这里的长度是8,也就是2的3次幂。这样的数组长度才会出现一个0111除高位以外都是1的特征,也是为了散列。接下来就是循环存放数据,计算出每个字符串在数组中的位置。key。hashCode()(tab。length1)。在字符串存放到数组的过程,如果遇到相同的元素,进行连接操作模拟链表的过程。最后输出存放结果。
  测试结果key值jlkkIdx2key值lopiIdx4key值小傅哥Idx7key值e4weIdx5key值alpoIdx2key值yhjkIdx0key值plopIdx5测试结果:〔yhjk,null,jlkkalpo,null,lopi,e4weplop,null,小傅哥〕在测试结果首先是计算出每个元素在数组的Idx,也有出现重复的位置。最后是测试结果的输出,1、3、6,位置是空的,2、5,位置有两个元素被链接起来e4weplop。这就达到了我们一个最基本的要求,将串元素散列存放到数组中,最后通过字符串元素的索引ID进行获取对应字符串。这样是HashMap的一个最基本原理,有了这个基础后面就会更容易理解HashMap的源码实现。1。2Hash散列示意图
  如果上面的测试结果不能在你的头脑中很好地建立出一个数据结构,那么可以看以下这张散列示意图,方便理解;
  bugstack。cnHash散列示意图这张图就是上面代码实现的全过程,将每一个字符串元素通过Hash计算索引位置,存放到数组中。黄色的索引ID是没有元素存放、绿色的索引ID存放了一个元素、红色的索引ID存放了两个元素。1。3这个简单的HashMap有哪些问题
  以上我们实现了一个简单的HashMap,或者说还算不上HashMap,只能算做一个散列数据存放的雏形。但这样的一个数据结构放在实际使用中,会有哪些问题呢?这里所有的元素存放都需要获取一个索引位置,而如果元素的位置不够散列碰撞严重,那么就失去了散列表存放的意义,没有达到预期的性能。在获取索引ID的计算公式中,需要数组长度是2的幂次方,那么怎么进行初始化这个数组大小。数组越小碰撞的越大,数组越大碰撞的越小,时间与空间如何取舍。目前存放7个元素,已经有两个位置都存放了2个字符串,那么链表越来越长怎么优化。随着元素的不断添加,数组长度不足扩容时,怎么把原有的元素,拆分到新的位置上去。
  以上这些问题可以归纳为;扰动函数、初始化容量、负载因子、扩容方法以及链表和红黑树转换的使用等。接下来我们会逐个问题进行分析。2。扰动函数
  在HashMap存放元素时候有这样一段代码来处理哈希值,这是java8的散列值扰动函数,用于优化散列效果;staticfinalinthash(Objectkey){inth;return(keynull)?0:(hkey。hashCode())(h16);}2。1为什么使用扰动函数
  理论上来说字符串的hashCode是一个int类型值,那可以直接作为数组下标了,且不会出现碰撞。但是这个hashCode的取值范围是〔2147483648,2147483647〕,有将近40亿的长度,谁也不能把数组初始化得这么大,内存也是放不下的。
  我们默认初始化的Map大小是16个长度DEFAULTINITIALCAPACITY14,所以获取的Hash值并不能直接作为下标使用,需要与数组长度进行取模运算得到一个下标值,也就是我们上面做的散列列子。
  那么,hashMap源码这里不只是直接获取哈希值,还进行了一次扰动计算,(hkey。hashCode())(h16)。把哈希值右移16位,也就正好是自己长度的一半,之后与原哈希值做异或运算,这样就混合了原哈希值中的高位和低位,增大了随机性。计算方式如下图;
  bugstack。cn扰动函数说白了,使用扰动函数就是为了增加随机性,让数据元素更加均衡的散列,减少碰撞。2。2实验验证扰动函数
  从上面的分析可以看出,扰动函数使用了哈希值的高半区和低半区做异或,混合原始哈希码的高位和低位,以此来加大低位区的随机性。
  但看不到实验数据的话,这终究是一段理论,具体这段哈希值真的被增加了随机性没有,并不知道。所以这里我们要做一个实验,这个实验是这样做;选取10万个单词词库定义128位长度的数组格子分别计算在扰动和不扰动下,10万单词的下标分配到128个格子的数量统计各个格子数量,生成波动曲线。如果扰动函数下的波动曲线相对更平稳,那么证明扰动函数有效果。2。2。1扰动代码测试
  扰动函数对比方法publicclassDisturb{publicstaticintdisturbHashIdx(Stringkey,intsize){return(size1)(key。hashCode()(key。hashCode()16));}publicstaticinthashIdx(Stringkey,intsize){return(size1)key。hashCode();}}disturbHashIdx扰动函数下,下标值计算hashIdx非扰动函数下,下标值计算
  单元测试10万单词已经初始化到words中Testpublicvoidtestdisturb(){MapInteger,IntegermapnewHashMap(16);for(Stringword:words){使用扰动函数intidxDisturb。disturbHashIdx(word,128);不使用扰动函数intidxDisturb。hashIdx(word,128);if(map。containsKey(idx)){Integerintegermap。get(idx);map。put(idx,integer);}else{map。put(idx,1);}}System。out。println(map。values());}
  以上分别统计两种函数下的下标值分配,最终将统计结果放到excel中生成图表。2。2。2扰动函数散列图表
  以上的两张图,分别是没有使用扰动函数和使用扰动函数的,下标分配。实验数据;10万个不重复的单词128个格子,相当于128长度的数组
  未使用扰动函数
  bugstack。cn未使用扰动函数
  使用扰动函数
  bugstack。cn使用扰动函数从这两种的对比图可以看出来,在使用了扰动函数后,数据分配的更加均匀了。数据分配均匀,也就是散列的效果更好,减少了hash的碰撞,让数据存放和获取的效率更佳。3。初始化容量和负载因子
  接下来我们讨论下一个问题,从我们模仿HashMap的例子中以及HashMap默认的初始化大小里,都可以知道,散列数组需要一个2的幂次方的长度,因为只有2的幂次方在减1的时候,才会出现01111这样的值。
  那么这里就有一个问题,我们在初始化HashMap的时候,如果传一个17个的值newHashMap(17);,它会怎么处理呢?3。1寻找2的幂次方最小值
  在HashMap的初始化中,有这样一段方法;publicHashMap(intinitialCapacity,floatloadFactor){。。。this。loadFactorloadFactor;this。thresholdtableSizeFor(initialCapacity);}阈值threshold,通过方法tableSizeFor进行计算,是根据初始化来计算的。这个方法也就是要寻找比初始值大的,最小的那个2进制数值。比如传了17,我应该找到的是32(2的4次幂是1617,所以找到2的5次幂32)。
  计算阈值大小的方法;staticfinalinttableSizeFor(intcap){intncap1;nn1;nn2;nn4;nn8;nn16;return(n0)?1:(nMAXIMUMCAPACITY)?MAXIMUMCAPACITY:n1;}MAXIMUMCAPACITY130,这个是临界范围,也就是最大的Map集合。乍一看可能有点晕怎么都在向右移位1、2、4、8、16,这主要是为了把二进制的各个位置都填上1,当二进制的各个位置都是1以后,就是一个标准的2的幂次方减1了,最后把结果加1再返回即可。
  那这里我们把17这样一个初始化计算阈值的过程,用图展示出来,方便理解;
  bugstack。cn计算阈值3。2负载因子staticfinalfloatDEFAULTLOADFACTOR0。75f;
  负载因子是做什么的?
  负载因子,可以理解成一辆车可承重重量超过某个阈值时,把货放到新的车上。
  那么在HashMap中,负载因子决定了数据量多少了以后进行扩容。这里要提到上面做的HashMap例子,我们准备了7个元素,但是最后还有3个位置空余,2个位置存放了2个元素。所以可能即使你数据比数组容量大时也是不一定能正正好好的把数组占满的,而是在某些小标位置出现了大量的碰撞,只能在同一个位置用链表存放,那么这样就失去了Map数组的性能。
  所以,要选择一个合理的大小下进行扩容,默认值0。75就是说当阈值容量占了34时赶紧扩容,减少Hash碰撞。
  同时0。75是一个默认构造值,在创建HashMap也可以调整,比如你希望用更多的空间换取时间,可以把负载因子调的更小一些,减少碰撞。4。扩容元素拆分
  为什么扩容,因为数组长度不足了。那扩容最直接的问题,就是需要把元素拆分到新的数组中。拆分元素的过程中,原jdk1。7中会需要重新计算哈希值,但是到jdk1。8中已经进行优化,不再需要重新计算,提升了拆分的性能,设计的还是非常巧妙的。4。1测试数据TestpublicvoidtesthashMap(){ListStringlistnewArrayList();list。add(jlkk);list。add(lopi);list。add(jmdw);list。add(e4we);list。add(io98);list。add(nmhg);list。add(vfg6);list。add(gfrt);list。add(alpo);list。add(vfbh);list。add(bnhj);list。add(zuio);list。add(iu8e);list。add(yhjk);list。add(plop);list。add(dd0p);for(Stringkey:list){inthashkey。hashCode()(key。hashCode()16);System。out。println(字符串:keyIdx(16):((161)hash)Bit值:Integer。toBinaryString(hash)Integer。toBinaryString(hash16)Idx(32):((System。out。println(Integer。toBinaryString(key。hashCode())Integer。toBinaryString(hash)Integer。toBinaryString((321)hash));}}
  测试结果字符串:jlkkIdx(16):3Bit值:110001110100100001001110000Idx(32):191100011101001000100010110001110100100001001110011字符串:lopiIdx(16):14Bit值:11001011000110100011100Idx(32):14110010110001101011110011001011000110100011101110字符串:jmdwIdx(16):7Bit值:11000111010101001001110Idx(32):711000111010101000101101100011101010100100111111字符串:e4weIdx(16):3Bit值:101110101110110101001110000Idx(32):191011101011101101111101101110101110110101001110011字符串:io98Idx(16):4Bit值:110001011000101111010010000Idx(32):201100010110001011000101110001011000101111010010100字符串:nmhgIdx(16):13Bit值:11001110100110110011010Idx(32):13110011101001101111111011001110100110110011011101字符串:vfg6Idx(16):8Bit值:11011100101111011010000Idx(32):8110111001011110101111111011100101111011010001000字符串:gfrtIdx(16):1Bit值:110000010111110101000110000Idx(32):171100000101111101100001110000010111110101000110001字符串:alpoIdx(16):7Bit值:10110110111011010001110Idx(32):710110110111011011010101011011011101101000111111字符串:vfbhIdx(16):1Bit值:11011100101110110000010Idx(32):1110111001011101111011011011100101110110000011字符串:bnhjIdx(16):0Bit值:10111000110110011000000Idx(32):0101110001101100100111010111000110110011000000字符串:zuioIdx(16):8Bit值:111001001110011001100010000Idx(32):241110010011100110100001111001001110011001100011000字符串:iu8eIdx(16):8Bit值:11000101111001011010000Idx(32):8110001011110010101100111000101111001011010001000字符串:yhjkIdx(16):8Bit值:11100010010100101010000Idx(32):8111000100101001001000011100010010100101010001000字符串:plopIdx(16):9Bit值:11010010001100111010010Idx(32):9110100100011001101110111010010001100111010011001字符串:dd0pIdx(16):14Bit值:10111011110010111011100Idx(32):14101110111100101100000010111011110010111011101110这里我们随机使用一些字符串计算他们分别在16位长度和32位长度数组下的索引分配情况,看哪些数据被重新路由到了新的地址。同时,这里还可以观察出一个非常重要的信息,原哈希值与扩容新增出来的长度16,进行运算,如果值等于0,则下标位置不变。如果不为0,那么新的位置则是原来位置上加16。{这个地方需要好好理解下,并看实验数据}这样一来,就不需要在重新计算每一个数组中元素的哈希值了。4。2数据迁移
  bugstack。cn数据迁移这张图就是原16位长度数组元素,向32位扩容后数组转移的过程。对31取模保留低5位,对15取模保留低4位,两者的差异就在于第5位是否为1,是的话则需要加上增量,为0的话则不需要改变其中黄色区域元素zuio因计算结果hasholdCap低位第5位为1,则被迁移到下标位置24。同时还是用重新计算哈希值的方式验证了,确实分配到24的位置,因为这是在二进制计算中补1的过程,所以可以通过上面简化的方式确定哈希值的位置。
  那么为什么e。hasholdCap0为什么可以判断当前节点是否需要移位,而不是再次计算hash;
  仍然是原始长度为16举例:old:10:0000101015:00001111:00001010new:10:0000101031:00011111:00001010
  从上面的示例可以很轻易的看出,两次indexFor()的差别只是第二次参与位于比第一次左边有一位从0变为1,而这个变化的1刚好是oldCap,那么只需要判断原key的hash这个位上是否为1:若是1,则需要移动至oldCapi的槽位,若为0,则不需要移动;
  这也是HashMap的长度必须保证是2的幂次方的原因,正因为这种环环相扣的设计,HashMap。loadFactor的选值是34就能理解了,table。length34可以被优化为((table。length2)2)(table。length2)table。length(table。length2),JAVA的位运算比乘除的效率更高,所以取34在保证hash冲突小的情况下兼顾了效率;四、总结如果你能坚持看完这部分内容,并按照文中的例子进行相应的实验验证,那么一定可以学会本章节涉及这五项知识点;1、散列表实现、2、扰动函数、3、初始化容量、4、负载因子、5、扩容元素拆分。对我个人来说以前也知道这部分知识,但是没有验证过,只知道概念如此,正好借着写面试手册专栏,加深学习,用数据验证理论,让知识点可以更加深入的理解。这一章节完事,下一章节继续进行HashMap的其他知识点挖掘,让懂了就是真的懂了。好了,写到这里了,感谢大家的阅读。如果某处没有描述清楚,或者有不理解的点,欢迎与我讨论交流。

4种食物或是偷钙大王,越吃骨头越松,年过五十,建议少吃我每年都会给自己补充钙,尤其是当我年过五十时,钙的补充就更重要了。为此,我会吃一些富含钙的食物,比如牛奶豆腐芝麻和菠菜。另外,我也会偶尔吃一些偷钙大王,因为它的钙含量也很高,而且越糖友们注意这3类食物少入口,血糖才能只降不升有糖尿病的情况下积极配合治疗,作为顽固,影响广泛的慢性疾病之一,许多人各种并发症出现都是因为疾病应对不当,严重时会带来糖尿病性心脏病,糖尿病足,因此不能大意,除了掌握合理用药方法,生化危机T病毒是什么?怎么一个原理能把人变成丧尸?历史上最早的生化武器T病毒,是CAPCOM著名恐怖游戏生化危机中的一种生化病毒。(暴君病毒)。是一种新型的RNA病毒,保护伞公司三大元老之一的詹姆斯马库斯通过将早期发现的神秘古代病整活真有效果!小春晚IG20击败RNG,他每年都能找到这种上单吗?新赛季新气象,咱们LPL新赛季已经开启有几天了,最近的比赛真是一场比一场精彩!同时在选手的登场方式上面,咱们LPL竟然开始卷起来了!刚开始大家还都是简单的整一下活,摆几个Pose就朱古达战争政治与战争,战争对社会发展具有独特作用?引言在罗马史上曾经爆发过一场以朱古达命名的一场战争,朱古达是罗马属国的一个国王的养子,为了争夺王位与罗马元老院产生了不可调和的矛盾,最终爆发了一场长达七年的战争。战争是人类社会无法大展宏兔全球使命3春节活动福利盘点初升的太阳照耀着千家万户,人们都在为即将到来的春节忙着贴春联大扫除张灯结彩。在春节气氛下,全视角PVE射击网游全球使命3重磅推出了多个春节版本活动,玩家在体验全新版本春节资料片趣味零陵赵立平专题调研区委党校各项工作红网时刻新闻1月18日讯(通讯员杨文哲杨情陆)1月17日上午,零陵区委书记赵立平在区委党校进行专题调研时要求,要始终坚持党校姓党,将学习宣传贯彻党的二十大精神作为主轴主线,紧紧围绕你知道吗?今年春节,是本世纪第二早春节春节的脚步日益临近,1月22日,我们将迎来2023年春节。天文科普专家介绍,2023年春节是21世纪这100年里的第二早春节,比21世纪最早春节只晚了一天。春节是每年农历的正月初一大屏高刷高清,这三款ROG显示器,让春节快乐不止加倍!春节马上来临,相信很多小伙伴为了健康和安全,这个春节还是会继续宅家。如何度过这7天的假期,喜欢游戏的小伙伴想必少不了趁着假期嗨玩起来。不过工欲善其事必先利其器,今天就为大家带来几款苹果发布2023年第一波新品搭载M2升级芯片的MacbookPro和Macmini记者徐诗琪编辑1月17日,苹果公司发布了2023年第一波新品,分别为搭载M2Pro与M2Max芯片的新款14与16英寸MacBookPro,和搭载M2与M2Pro的新款Macmin数据库数据恢复华为云mysql数据库误删除的数据恢复案例数据库数据恢复环境华为云ECS,linux操作系统mysql数据库,实例内数据表默认存储引擎为innodb。北亚数据恢复mysql数据恢复数据库故障在执行数据库版本更新测试时,用户
美将中国无人机列入黑名单,盟友英国不仅不遵守,还下了大单无论是在欧洲区域,还是北约体制内,英国都堪称是美国的铁杆盟友。在针对俄罗斯时,美国点火成功后,英国马上跟进,共同发起制裁在针对中国时,美国施压后,英国即便挣扎,最后还是禁用了华为。担心的事还是发生了!1岁多男童吃了根烤肠,竹签刺入颅脑来源都市现场综合淮安零距离网浙江之声新闻夜航近日,江苏1岁男童毛毛边吃烤肠边跑不慎摔跤烤肠的竹签刺入鼻腔竹签长10厘米其中1厘米已刺入颅脑若刺破脑血管后果将不堪设想男童就诊过程中已黎巴嫩警告以色列不要在争议水域有挑衅行为据路透社报道,黎巴嫩5日警告以色列不要在争议海域采取任何攻击行动。据悉,黎巴嫩和以色列都希望在争议海域开发近海能源。此前,英国埃纳金公司的一艘船抵达近海,以为以色列生产天然气。黎巴欧国联克罗地亚VS法国九又四分之三站台美联社记者会为淘汰乌克兰道歉吗?贝尔听起来很可怕,但我不会道歉。皇马BBC齐聚卡塔尔世界杯,2022年的冬季注定会不同凡响!贝尔的一记绝妙任意球,送走乌克兰人(长舒一口气)的同时,河南脑出血死亡女孩父亲发声卫健委希望平静处理6月4日上午9点,河南大学脑出血死亡大三学生彭某君的父亲彭先生从平顶山赶到郑州,下午4点在入住的宾馆里与郑州市卫健委代表120急救中心代表以及河南大学相关校区的领导就彭某君脑出血死广东近10年(20112021年广东高考作文题目全国新高考卷试题内容阅读下面的材料,根据要求写作。1917年4月,毛泽东在新青年发表体育之研究一文,其中论及体育之效时指出人的身体会天天变化。目不明可以明日本为保护女性彻底封杀AV?别被他们忽悠了近日有网友称,日本议员提出要设立禁止AV法案了!LSP们先别急着哀嚎,静静看一下,整个事件过程是相当离谱,堪称多米诺骨牌效应。图片来源YAHOO网首先,起因是今年的4月1日,日本新医生提醒三个症状不断出现,或许是直肠癌!早做检查为妙老刘是一位在家务农几十年的老农民,今年计划要把菜地都种上孙子爱吃的菜,但是老刘的身体并不允许他自己完成种菜前耕地的任务,原来老刘是一个身体还比较健康的老人,但是自从去年老刘满了六十喝水后一直小便的人,和半天不去厕所的人,哪一种人身体更健康?有关专家曾做出过这样一项有趣的实验,他们发现一个人在未进食任何食物的情况下,大约可以维持7天的寿命,若一个人在不喝水的情况下,只能维持三天的生活,由此也可看出水对人的重要性究竟有多塔图姆谈输球客场作战是更艰难的环境G3我们必须要打得更好直播吧6月6日讯今日NBA总决赛G2,凯尔特人88107客场不敌勇士,系列赛大比分11平。赛后,凯尔特人球星塔图姆接受了记者采访。谈到输球,塔图姆讲道你在客场作战,这是一个更艰难的戏弄女性算不雅?家长指责新华字典导向不良,却被指吹毛求疵文晓宁说教育不能让有毒教材影响学生的心灵和成长。人教版数学教材的事情,在网络上掀起了轩然大波,不仅引发了家长和学生们的关注,也引起了社会人士的高度重视。教材的插图符合大众的审美,传
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网