游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

面试记录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的其他知识点挖掘,让懂了就是真的懂了。好了,写到这里了,感谢大家的阅读。如果某处没有描述清楚,或者有不理解的点,欢迎与我讨论交流。

Z218次列车兰州站上海站Z218次列车是由甘肃省省会兰州市开往直辖市上海市的一列直达普速列车,该车运行时间24小时35分钟。14:20从兰州火车站出发15:36到达定西站,停车4分钟,15……入夏了,把姜蒸一蒸,外婆这招真厉害,不花钱还管用,很多人不懂入夏了,把姜蒸一蒸,外婆这招真厉害,不花钱还管用,很多人不懂大家都听说过冬吃萝卜夏吃姜,但你知道为什么夏天要吃姜吗?姜又怎么吃呢?今天我们就来聊聊吃姜。夏天已经来了……旅行时,女孩的行李箱里最好带上这些小东西,安全卫生不尴尬现在交通发达了,每个人的经济都很好,所以旅行已经成为一种非常流行的娱乐方式,但对女孩来说,旅行手提箱永远不够大,衣服、裙子、帽子、鞋子、化妆品和护肤产品首饰,毕竟,除了旅行,还……2022年我国人口大概率负增长,楼市的又一轮抢人大战即将开始数据显示,我国已经进入到中度老龄化社会,60岁以上的老年人口达到了约2。7亿,占总人口的比重达到18。另外新生儿的数量越来越少,到了2021年人口净增量只有48万人。2022年……痛风专方痛风汤药物组成:刺五加30克,红景天30克,生黄芪30克,土茯苓30克,萆薢20克,薏苡仁30克,蚕砂30克,片姜黄15克,威灵仙10克,虎杖15克,秦艽15克,独活15克,佛……五一假期能出游吗?最新指引五一长假前广州成功摘星至此,广东全省没有中高风险地区全域低风险!广东可恢复跨省旅游吗?五一假期能正常出游吗?权威人士提醒五一期间,应倡……颜丙涛136领先北京时间4月25日凌晨,2022年斯诺克世锦赛第二轮继续进行。夺冠大热门贾德特鲁姆普,在与13号种子安东尼麦克吉尔的第二阶段结束后,106领先对手,再胜三局就将晋级8强。稍早前……干货分享花海项目不是简单的花卉种植,这3大问题也要考虑春暖花开的季节,踏春赏花是我们的不二之选,近期各地的花海登上热门,有金色的油菜花花海;粉色的樱花花海、桃花花海、海棠花花海;白色的白玉兰花海、梨花花海、杏花花海;紫红色的紫荆花……生我养我的村庄(我的乡亲)我熟悉你褐黄深厚的土壤,略带甜味的水和干燥温暖的空气,熟悉你天空的每一朵云彩,陆地的每一块石头,还有静谧的夜空中头顶的星和身边蛐蛐的叫声。我熟悉在你怀抱里生长的每一种植物,还有……全靠高油价衬托?造车新势力5月份销量纷纷暴涨第一名:广汽埃安5月份,广汽埃安的销量达到了21056辆,创下了品牌历史新高,同比增长150,稳居新势力榜首。这也是唯一一个销量破两万的造车新势力品牌,作为传统车企孵化的……漫威演员首部作品和现如今的模样对比,冬兵我直接看笑了漫威电影宇宙发展的这些年里,从新人演员到奥斯卡最佳男女主演员,基本上都有收集,有些作品中的演员是很多影迷一直都很喜欢的,有些则是粉丝从漫威发掘出来之后,逐渐迷上的。不过,……财政部对10个地区调研显示组合式税费支持政策成效显著今年,我国部署实施新的组合式税费支持政策。财政部近期对10个地区的工作情况开展调研,调研结果及税务总局的统计数据显示,组合式税费支持政策成效显著,对减轻企业负担、激发创新活力、……
在质子内部发现比质子更重的魅夸克,意味着什么?(全文约5000字,看完需5分钟)每个质子包含三个夸克:两个上夸克和一个下夸克。但是前阵子科学家们在内部发现了比质子本身更重的魅夸克,这意味着什么?(图片来源:阿贡……核潜艇模拟器NuclearSubSteam正式上线文Fama游戏发行商PlayWay发布由NuclearGames开发的新游戏《核潜艇模拟器:NuclearSub》并公开了Steam专页,游戏支持简体中文。本作是一款让玩……因伤无缘战篮网,詹姆斯本周能拿NBA历史得分王头衔吗?新京报讯1月31日8时30分,NBA常规赛一场焦点战,湖人将在客场挑战篮网。湖人今日更新球队伤病报告,詹姆斯左脚踝酸痛,安东尼戴维斯因右脚伤势,均将缺席明日一战。湖人本周……2022年张掖经济交出亮眼成绩单新甘肃甘肃经济日报记者郑朝华日前,张掖市对外通报,2022年,全市主要经济指标在全省的位次逐级前移,绝大部分指标高于全国全省平均水平,地区生产总值增长6。1,居全省第四位……惹众怒!无限期禁言近日,游客私闯纳木错景区的话题登上微博热搜,引发争议。2月5日,抖音官方发布关于打击违规闯入旅游景点等行为的公告,6个账号被无限期禁言,呼吁游客理性打卡观光。此外,……走心!纪念冬奥一周年宣传歌曲那年冬天我们一起走过破圈传唱2023年2月4日,在纪念北京2022年冬奥会成功举办一周年之际,北京奥运城市发展促进中心、北京广播电视台联合制作宣传歌曲《那年冬天我们一起走过》。歌曲由田田、木子、饶隽作词,……嘿嘿,我要飘了,雇主宝宝今天返校,宝哥随宝妈出差几天萌娃成长记昨天早上起来和往常一样,我最先起床,洗漱好就在厨房烧开水,做早餐。反正每天炖蛋,白煮蛋,荷包蛋,花卷,包子,发面饼,鸡蛋饼,葱油饼,面条,年糕,蛋炒饭,水……谁是河北第三城,沧州,邯郸还是保定?河北有11个地级市,除了唐山,石家庄处在第一方队以外。还有三个城市沧州,邯郸,保定处在第二集团,都刚刚突破4000亿大关,关于谁是河北第三城的争论由来已久,我们今天就来盘盘谁更……学会不要脸,才能更强大!(挺有道理)做人,就要学会不要脸,这样,你才能变得更强大。一个人,如果脸皮太薄,这也不好意思,那也不好意思,那么这个人是很难有所成就的,而且他的内心也不强大。据我观察,现实生活中,那……不同阶段下,就业和经济增长的关系有哪些变化?不同阶段下就业和经济增长关系变化1就业弹性的阶段性变化分析描述经济增长对就业带动作用的关键指标就是就业弹性。就业弹性指某一阶段就业增长速率与经济增长速率的比值,即经……曝多支球队愿为马利克比斯利付出首轮签北京时间2月4日,据名记克里斯海恩斯报道,有联盟高管透露,目前已有多支球队愿为从爵士得到马利克比斯利而付出一个首轮签。此前有报道称,比斯利目前已经吸引了多支球队的关注,像……在不抵达比邻星b的情况下,如何判断这颗星球是否存在智慧生命?一般认为,三体的原型,就是距离地球4光年开外的比邻星b。比邻星b位于该星系的宜居带内,因此,有可能孕育出生命甚至智慧生命。不过,限于人类的技术水平局限,目前人类是无法派遣任何探……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网