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

深度剖析HashMap的底层实现

  头条创作挑战赛概述
  HashMap作为Java程序员使用频率非常高的容器,同时,同时也是面试官非常爱问的,里面的知识点满满,需要我们对它的实现机制有个深入的理解,本文主要通过jdk8带领大家剖析下HashMap。HashMap简介
  HashMap最早出现在JDK1。2中,底层基于散列算法实现,它是一个keyvalue结构的容器。是一个keyvalue的映射容器,key不重复jdk8中的HashMap基于数组链表红黑树实现不保证键值的顺序可以存入null值非线程安全,多线程环境下可能存在问题
  以上是HashMap的类结构图:继承了AbstractMap,实现了Map接口,提供了key,value结构格式访问的方法实现了Cloneable接口,表示HashMap支持clone实现了Serializable接口,表示HashMap支持序列化核心机制底层实现机制
  jdk8中的HashMap底层数据才有数组链表红黑树的方式实现。扩容机制
  HashMap底层是一个数组,Java中的数组是固定的,随着我们往HashMap中添加元素,发现数组长度不够了,这时候就需要进行扩冲容量的操作,和扩容相关的参数有两个一个是初始容量initialCapacity,另一个负载因子loadFactor。通过这两个设定这两个参数,可以进一步影响阈值大小。扩容的阈值threshold等于容量负载因子(thresholdcapacityloadFactor)。
  名称
  用途
  initialCapacity
  HashMap初始容量
  loadFactor
  负载因子
  threshold
  当前HashMap所能容纳键值对数量的最大值,超过这个值,则需扩容快速失败机制
  HashMap遍历使用的是一种快速失败机制,它是Java非安全集合中的一种普遍机制,这种机制可以让集合在遍历时,如果有线程对集合进行了修改、删除、增加操作,会触发并发修改异常。
  它的实现机制是在遍历前保存一份modCount,在每次获取下一个要遍历的元素时会对比当前的modCount和保存的modCount是否相等。
  快速失败也可以看作是一种安全机制,这样在多线程操作不安全的集合时,由于快速失败的机制,会抛出异常。这样可以避免由于并发修改导致一些未知的问题,通过提前失败提高性能。源码剖析成员变量
  成员变量可以说明HashMap的底层数据结构。底层存储的数据结构,是一个Node数组transientNodeK,V〔〕table;遍历用到的entrySettransientSetMap。EntryK,VentrySet;hashmap的元素数量transientintsize;修改次数,用于快速失败机制transientintmodCount;发生扩容的阈值intthreshold;扩容使用的负载因子serialfinalfloatloadFactor;
  我们再来看下Node的数据结构,实现了Map。Entry接口。
  很明显是一个链表的结构,红黑树也是基于这个数据结构构建得到。构造方法
  有参构造函数源码如下,关键是tableSizeFor这个方法publicHashMap(intinitialCapacity,floatloadFactor){if(initialCapacity0)thrownewIllegalArgumentException(Illegalinitialcapacity:initialCapacity);if(initialCapacityMAXIMUMCAPACITY)initialCapacityMAXIMUMCAPACITY;if(loadFactor0Float。isNaN(loadFactor))thrownewIllegalArgumentException(Illegalloadfactor:loadFactor);this。loadFactorloadFactor;根据tableSizeFor获取扩容阈值this。thresholdtableSizeFor(initialCapacity);}staticfinalinttableSizeFor(intcap){intncap1;nn1;nn2;nn4;nn8;nn16;return(n0)?1:(nMAXIMUMCAPACITY)?MAXIMUMCAPACITY:n1;}
  该方法的作用总结起来就一句话:找到大于或等于cap的最小2的幂。至于为啥要这样,后面再解释。我们先来看看tableSizeFor方法的图解:
  可以理解为把cap低位的二进制位通过右移全部变为1,最后再1,就是2的幂次方了。
  此时这里的阈值threshold不是初始容量负载因子,不必在意,这只是临时的,真正设置threshold在后面put方法中。put方法
  其实整个向map中插入数据的流程,大家多少都应知道一些,整个流程如上图所示,我们现在通过源码解读理解这个过程中的细节。
  put方法对外暴露的接口,添加的入口publicVput(Kkey,Vvalue){核心是调用putVal方法,参数的hash方法是计算key的hash值returnputVal(hash(key),key,value,false,true);}
  hash方法staticfinalinthash(Objectkey){inth;采用位运算获取最终的hashreturn(keynull)?0:(hkey。hashCode())(h16);}
  这段代码叫做扰动函数,也是hashMap中的hash运算,主要分为下面几步:key。hashCode()获取key的hashCode值,如果不进行重写的话返回的是根据内存地址得到的一个int值。key。hashCode()获取到的hashCode无符号右移16位并和原hashCode进行,这样做的目的是为了让高位与低进行混合,让两者都参与运算,以便让hash值分布更加均匀。
  putVal方法finalVputVal(inthash,Kkey,Vvalue,booleanonlyIfAbsent,booleanevict){NodeK,V〔〕tab;NodeK,Vp;intn,i;如果数组为空,进行resize()初始化,后面详细分析resize方法if((tabtable)null(ntab。length)0)n(tabresize())。length;(n1)hash相当于取模,获取数组的索引位置如果计算的位置上Node不存在,直接创建节点插入if((ptab〔i(n1)hash〕)null)tab〔i〕newNode(hash,key,value,null);else{如果计算的位置上Node存在,链表或者红黑树处理NodeK,Ve;Kk;如果已存在的key和传入的key一模一样,则需要覆盖if(p。hashhash((kp。key)key(key!nullkey。equals(k))))ep;如果index位置元素已经存在,且是红黑树elseif(pinstanceofTreeNode)将元素插入到红黑树中e((TreeNodeK,V)p)。putTreeVal(this,tab,hash,key,value);else{否则如果是链表的情况,对链表进行遍历,并统计链表长度for(intbinCount0;;binCount){如果节点链表的next为空if((ep。next)null){找到节点链表中next为空的节点,创建新的节点插入p。nextnewNode(hash,key,value,null);如果节点链表中数量超过TREEIFYTHRESHOLD(8)个,转化为红黑树if(binCountTREEIFYTHRESHOLD1)1for1st树化操作treeifyBin(tab,hash);break;}判断节点链表中的key和传入的key是否一样if(e。hashhash((ke。key)key(key!nullkey。equals(k))))如果一样的话,退出break;pe;}}如果存在相同key的节点e不为空if(e!null){existingmappingforkeyVoldValuee。value;onlyIfAbsent表示是否仅在oldValue为null的情况下更新键值对的值if(!onlyIfAbsentoldValuenull)设置新的值e。valuevalue;afterNodeAccess(e);返回老的结果returnoldValue;}}modCount;当前大小大于临界大小,扩容if(sizethreshold)resize();afterNodeInsertion(evict);returnnull;}
  putVal方法主要做了这么几件事情:当桶数组table为空时,通过扩容的方式初始化table。查找要插入的键值对是否已经存在,存在的话根据条件判断是否用新值替换旧值。如果不存在,则将键值对链入链表中,并根据链表长度决定是否将链表转为红黑树。判断键值对数量是否大于阈值,大于的话则进行扩容操作。
  resize()方法
  当HashMap中的键值对数量超过扩容阈值时,则进行扩容,先阐述清楚几个概念:容量:表示HashMap中数组的长度扩容阈值:表示HashMap中数组有值的数量超过这个阈值,则需要进行扩容处理,扩容阈值等于容量负载因子。finalNodeK,V〔〕resize(){NodeK,V〔〕oldTabtable;现有容量的大小,等于数组的长度,如果数组为空,返回0intoldCap(oldTabnull)?0:oldTab。length;现有的扩容阈值intoldThrthreshold;newCap表示新的容量,newThr新的扩容阈值intnewCap,newThr0;如果现有容量大于0,表示已经初始化过了if(oldCap0){如果现有容量已经大于最大容量。结束扩容,直接返回if(oldCapMAXIMUMCAPACITY){thresholdInteger。MAXVALUE;returnoldTab;}否则,如果扩大两倍之后的容量小于最大容量,且现有容量大于等于初始容量16elseif((newCapoldCap1)MAXIMUMCAPACITYoldCapDEFAULTINITIALCAPACITY)新的扩容阀值扩大为两倍,左移1相当于乘以2newThroldThr1;doublethreshold}否则如果当前容量等于0,但是当前扩容阈值0,调用有参构造函数会到这里elseif(oldThr0)initialcapacitywasplacedinthreshold进入这里,新的容量等于当前的扩容阈值,newCapoldThr;否则如果当前容量等于0,并且挡墙扩容阈值0,调用无参构造函数进入这里else{新的容量等于默认容量newCapDEFAULTINITIALCAPACITY;新的扩容阈值等于默认负载因子0。75默认容量1612newThr(int)(DEFAULTLOADFACTORDEFAULTINITIALCAPACITY);}如果新的扩容阈值等于0if(newThr0){设置新的扩容阈值等于新的容量负载因子floatft(float)newCaploadFactor;newThr(newCapMAXIMUMCAPACITYft(float)MAXIMUMCAPACITY?(int)ft:Integer。MAXVALUE);}设置hashmap对象的扩容阈值位新的扩容阈值thresholdnewThr;SuppressWarnings({rawtypes,unchecked})初始化数组NodeK,V〔〕newTab(NodeK,V〔〕)newNode〔newCap〕;设置hashmap对象的桶数组为newTabtablenewTab;下面时rehash的过程如果旧的桶数组不为空,则遍历桶数组,并将键值对映射到新的桶数组中if(oldTab!null){遍历老的数组for(intj0;joldCap;j){NodeK,Ve;如果数组索引位置不为空if((eoldTab〔j〕)!null){oldTab〔j〕null;如果节点下面没有链表或者红黑树if(e。nextnull)用新数组容量取模,设置到新数组中newTab〔e。hash(newCap1)〕e;如果节点是红黑树elseif(einstanceofTreeNode)需要对红黑树进行拆分((TreeNodeK,V)e)。split(this,newTab,j,oldCap);如果节点是红黑树else{preserveorderNodeK,VloHeadnull,loTailnull;NodeK,VhiHeadnull,hiTailnull;NodeK,Vnext;遍历链表,并将链表节点按原顺序根据高低位分组do{nexte。next;if((e。hasholdCap)0){if(loTailnull)loHeade;elseloTail。nexte;loTaile;}else{if(hiTailnull)hiHeade;elsehiTail。nexte;hiTaile;}}while((enext)!null);将分组后的链表映射到新桶中if(loTail!null){loTail。nextnull;newTab〔j〕loHead;}if(hiTail!null){hiTail。nextnull;newTab〔joldCap〕hiHead;}}}}}returnnewTab;}
  这个resize方法大致做了如下的事情:计算新桶数组的容量newCap和新阈值newThr。根据计算出的newCap创建新的桶数组,桶数组table也是在这里进行初始化的。将键值对节点重新映射到新的桶数组里。如果节点是TreeNode类型,则需要拆分红黑树。如果是普通链表节点,则节点按原顺序进行分组。
  这边在将链表节点进行rehash用了一个非常好的设计理念,扩容后长度为原hash表的2倍,于是把hash表分为两半,分为低位和高位,如果能把原链表的键值对,一半放在低位,一半放在高位,而且是通过e。hasholdCap0来判断,这个判断有什么优点呢?
  举个例子:n16,二进制为10000,第5位为1,e。hasholdCap是否等于0就取决于e。hash第5位是0还是1,这就相当于有50的概率放在新hash表低位,50的概率放在新hash表高位。
  链表树化treeifyBin
  jdk8中会将节点链表在一定的条件下转换成红黑树,主要是因为红黑树的搜索查询性能更好,会将时间复杂度从O(n)变成O(logn),代码如下将普通节点链表转换成树形节点链表finalvoidtreeifyBin(NodeK,V〔〕tab,inthash){intn,index;NodeK,Ve;桶数组容量小于MINTREEIFYCAPACITY,优先进行扩容而不是树化if(tabnull(ntab。length)MINTREEIFYCAPACITY)resize();elseif((etab〔index(n1)hash〕)!null){hd为头节点(head),tl为尾节点(tail)TreeNodeK,Vhdnull,tlnull;do{将普通节点替换成树形节点TreeNodeK,VpreplacementTreeNode(e,null);if(tlnull)hdp;else{p。prevtl;tl。nextp;}tlp;}while((ee。next)!null);将普通链表转成由树形节点链表if((tab〔index〕hd)!null)将树形链表转换成红黑树hd。treeify(tab);}}
  根据代码得出,在扩容过程中,树化要满足两个条件:链表长度大于等于8桶数组容量大于等于64,当桶数组容量比较小时,键值对节点hash的碰撞率可能会比较高,进而导致链表长度较长。这个时候应该优先扩容,而不是立马树化。get方法
  get方法相对来说就简单很多了,源码如下:publicVget(Objectkey){NodeK,Ve;调用getNode方法,hash(key)方法上面讲过,获取key对应的hash值return(egetNode(hash(key),key))null?null:e。value;}finalNodeK,VgetNode(inthash,Objectkey){NodeK,V〔〕tab;NodeK,Vfirst,e;intn;Kk;定位键值对所在桶的位置if((tabtable)!null(ntab。length)0(firsttab〔(n1)hash〕)!null){根据hash算法找到对应位置的第一个数据,如果是指定的key,则直接返回if(first。hashhashalwayscheckfirstnode((kfirst。key)key(key!nullkey。equals(k))))returnfirst;if((efirst。next)!null){如果该节点为红黑树,则通过树进行查找if(firstinstanceofTreeNode)return((TreeNodeK,V)first)。getTreeNode(hash,key);如果该节点是链表,则遍历查找到数据do{if(e。hashhash((ke。key)key(key!nullkey。equals(k))))returne;}while((ee。next)!null);}}returnnull;}
  大致逻辑如下:根据hash值查找到指定位置的数据。校验指定位置第一个节点的数据是key是否为传入的key,如果是直接返回第一个节点,否则继续查找第二个节点。如果数据是TreeNode(红黑树结构),直接通过红黑树查找节点数据并返回。如果是链表结构,循环查找所有节点,返回数据。如果没有找到符合要求的节点,返回null。
  这里前调用下通过(n1)hash相当于取模运算,即可算出桶的在桶数组中的位置,这是什么道理呢?
  举个例子说明吧,假设hash185,n16。计算过程示意图如下:
  1001换成10进制就是9,185165,这个前提成立时n必须是2的幂次方。总结
  本篇文章大致讲解了HashMap的源码和以及核心机制,其中里面还有很多细节和内容,需要大家花时间去自我学习。

遇见听到一首歌,让我想起了一个很久远的朋友,希望他是平安的幸福的有些东西错过就是错过了,永远都回不去了,感情这的东西,太执着也是伤人伤己,人生就是一条漫漫长路,碰见谁都是过客,只不过是山东一大学老师去世,年龄仅36岁,学校讣告死因,网友感到惋惜人,从出生开始就要习惯生老病死,生老病死是人生的定律,成往坏空是世界的定律,无常变异是万物的定律。人生就那么几十年,走好自己的路就要自己的思考,有坚定的意志,坚持自己的追求,不能放活得太累,如何才能不卷现在社会发展太快,人们的生活水平较前些年有了突飞猛进的提高,然而,幸福感获得感是否也正比例的提高了呢?身边的朋友们无不喊累叫屈,口罩这几年也确实给大家带来了很大的创伤,让原本不富裕难忘的梦那是个温馨无比的晚上,两个女孩子并躺在床上,年轻的脑袋凑在一起叽叽喳喳。初始,夕阳的余晖从二楼的窗户倾洒而来,栗棕的家具显得温柔又美好。刹那间,室内倾洒柔柔月光,透过玻璃便能窥见星人海人海,浮华年代待揭开以年轻的名义,奢侈地经历着一个不合格的人生阶段,拥抱着热情,怀念与岁月,若偶尔神伤,看看不远海港处那轮圆圆的明月。生活中所有将来都会变成再也回不去的美好,上学时觉得求学苦,毕业了才郊外赏冰,天寒地冻的快乐气温急转直下,京郊野外的河湖已经率先结上厚厚冰层,有的冰面上镶嵌着颗颗气泡,有大有小,重重叠叠连成串有的湖面还形成了漂亮的冰裂纹,晶莹剔透,清澈又神秘,等你来欣赏千里冰封的美丽。不中国飞鹤是如何打败洋品牌,成为国产奶粉之光的?被自家董事长冷友斌称为世界最贵奶粉的中国飞鹤(06186。HK),最近又有大动作!2022年12月22日,在鲜萃活性营养,更适合中国宝宝体质中国飞鹤60周年战略升级发布会上,飞鹤公八十亿人口的地球为什么不和平共处我用OneNote写的笔记整理分享2022年12月29日人类走到二十一世纪三十年代,文明,文化,科技,经济等社会核心元素已经到了高度发达的程度。普通人都知道,仅从大小来看,在浩瀚宇全面内战爆发后,永嘉乐清黄岩边游击根据地的重建永(嘉)乐(清)黄(岩)边区是括苍山地区中的重要根据地之一,地处永嘉乐清黄岩温岭等县边界,域辽阔,有连绵起伏的括苍山雁荡山太湖山作为依托。这里地广土瘠,人民贫苦不堪,但民性强悍,富不战不和不守,不死不降不走,六不总督叶名琛是如何炼成的?头条创作挑战赛在第一次鸦片战争中,清廷被迫签下了不平等的中英南京条约。由于中英两方翻译引起的歧义,英国人认为,战后他们可以进入作为通商口岸的城市。清廷则通告英国领事,城邑兼指城市内河北大山中发现空城,一栋栋楼房无人居住,原是一家军工厂三线建设是发生在上世纪1965年至1980年,短短的十五年间的一段历史往事。几百万三线人离开城市走进大山之中,在绝境之地建起了座座红砖黛瓦楼房,为祖国的国防事业立下赫赫战功。三线建
英语专业的学生最好走哪个方向?选择英教,双语还是翻译更好?作为一名翻译人员,多年的从业验让我感触很深,英语是英语专业毕业生两条腿走路强大的就业保障。如果凭借英语专业毕业证和学位证就业,看似可申请的岗位很多,但是哪一个也不占竞争优势。从传统事业单位工勤人员转到专业技术岗位,主管部门同意人社局不同意该怎么办?事业单位专业技术岗位聘用是由人社部门决定的,仅仅主管部门同意是没有用的。事业单位分为管理岗位专业技术岗位和工勤岗位。管理岗位就相当于公务员领导职务,需要有任免权限的部门进行任命。管六七十年代结婚办事都吃什么?那时没有全面重視启用智慧人材,更新创造生产力的发展。稍有能力的过囍事和大寿,亲朋好友及近邻,帮助送点贺礼物质类,就等于王母娘娘的盘桃宴会。猪肉抹油嘴,杂菜半饱穿肠过,待客绿色主家愁期货交易几年,刻苦钻研各种技术,为什么大部分人还是亏损状态?做期货,心里部分的因素也是非常重要的。在阳明心学中一个重要的理论是知行合一,但这句话经常被误解为理论与实践相结合。但是如果你读了传习录你就会知道,其实阳明先生说的意思是你能做到的才国企税前6000,技术岗,私企10000,技术管理岗,加班同样多,该去哪儿?感谢邀请回答。如果是茉莉,会选择去私企。当然也是因为茉莉已经从国企跳出来了。其实经过跳槽的几次,茉莉觉得什么企业性质不重要,私企也有一样入职后就好像进了保险箱,等待养老的国企也有天卡萨丁当年被移除沉默,是设计师的无意之举还是玩家的诉求?卡萨丁是一个曾经排位禁用率高达百分之97的中单英雄,经历多个版本削弱以后,最终被拳头重做掉了。英雄联盟中有许多英雄的沉默被移除掉了最初卡萨丁非常的bug,从图片中我们可以看到,当时河北最好喝的酒是什么酒?河北的地方酒品种很多,几乎各个地方都有过酒厂,燕赵之地,自古就喜欢喝酒,喜欢喝酒的地方就离不开酒厂。不过河北的水和粮,并不是酿酒的最好原材料,比贵州四川等地稍逊一筹,所以十大名酒里贵阳有哪些免费的公园?盘点贵阳8大著名湿地公园(景区)湿地公园被誉为城市之肺,对于一个生态宜居的城市而言,必不可少。贵阳就是这么一个环绕在湿地中的城市,这在全国乃至全世界都几乎绝无仅有,算得上是得天独厚住在内蒙古草原边上是什么感受?福大人为你解答。我在草原短暂待过几次。第一次是有生以来头次独自旅行,那是在2006年的秋天。一个人去了内蒙古东乌珠穆沁旗的边境草原学骑马,在一个很小的边境嘎查生活了15天。每天与蒙怎么打鞋柜,才能将空间利用最大化?建议如图所示,鞋柜可以放在厨房一侧,可以将墙体拆除,做成嵌入式衣帽柜。厨房可做推拉门,卫生间一侧可在衣柜背面做钢化玻璃。阻隔潮气。其实有厨房冰箱附近约70公分左右的柜子也够用,但是四川有哪些不难考的二本大学?既然是二本批次招生的院校,再不难考也要达到二本批次的分数线才能够报考的。所以说不存在所谓的难不难考,分数到了自然也就够了,因此简单的对往年存在二本批次招生的院校进行汇总!一方面因为
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网