最近快到金三银四了,不少小伙伴又开始忙忙碌碌找工作了,我也是,秉着为自己也为大家,整理了下hashMap的知识点,希望能对你有所帮助! 这里主要是对jdk1。7和jdk1。8的分析JDK1。7hashMap结构图 img1。7hashMap变量初始容量16负载因子0。75数组节点类型是EntryK,Vput函数过程publicVput(Kkey,Vvalue){容量为空则初始化if(tableEMPTYTABLE){inflateTable(threshold);}key为null则进行putkey为null的操作if(keynull)returnputForNullKey(value);求hash值根据hashCode再进行一些位运算降低hash冲突的概率inthashhash(key);根据元素hash值求索引intiindexFor(hash,table。length);for(EntryK,Vetable〔i〕;e!ee。next){O如果元素hash值和key都和桶上的第一个元素相同,则替换valueif(e。hashhash((ke。key)keykey。equals(k))){VoldValuee。e。e。recordAccess(this);returnoldV}}modC添加元素头插法(可能会造成死循环)addEntry(hash,key,value,i);}hash函数过程hash冲突的解决办法: 开放定址法 再哈希法 链地址法(拉链法,hashMap使用)Retrieveobjecthashcodeandappliesasupplementalhashfunctiontotheresulthash,whichdefendsagainstpoorqualityhashfunctions。ThisiscriticalbecauseHashMapusespoweroftwolengthhashtables,thatotherwiseencountercollisionsforhashCodesthatdonotdifferinlowerbits。Note:Nullkeysalwaysmaptohash0,thusindex0。finalinthash(Objectk){inthhashSif(0!hkinstanceofString){returnsun。misc。Hashing。stringHash32((String)k);}hk。hashCode();ThisfunctionensuresthathashCodesthatdifferonlybyconstantmultiplesateachbitpositionhaveaboundednumberofcollisions(approximately8atdefaultloadfactor)。h(h20)(h12);returnh(h7)(h4);}求索引位置indexFor函数jdk1。7求索引位置函数staticintindexFor(inth,intlength){assertInteger。bitCount(length)1:lengthmustbeanonzeropowerof2;returnh(length1);}扩容过程1。addEntry函数Addsanewentrywiththespecifiedkey,valueandhashcodetothespecifiedbucket。Itistheresponsibilityofthismethodtoresizethetableifappropriate。添加元素Subclassoverridesthistoalterthebehaviorofputmethod。voidaddEntry(inthash,Kkey,Vvalue,intbucketIndex){当前容量大于等于负载数且当前位置首个元素不为空if((sizethreshold)(null!table〔bucketIndex〕)){扩容为原来的两倍resize(2table。length);hash(null!key)?hash(key):0;bucketIndexindexFor(hash,table。length);}createEntry(hash,key,value,bucketIndex);}2。resize函数Rehashesthecontentsofthismapintoanewarraywithalargercapacity。Thismethodiscalledautomaticallywhenthenumberofkeysinthismapreachesitsthreshold。IfcurrentcapacityisMAXIMUMCAPACITY,thismethoddoesnotresizethemap,butsetsthresholdtoInteger。MAXVALUE。Thishastheeffectofpreventingfuturecalls。paramnewCapacitythenewcapacity,MUSTmustbegreaterthancurrentcapacityunlesscurrentcapacityisMAXIMUMCAPACITY(inwhichcasevalueisirrelevant)。voidresize(intnewCapacity){Entry〔〕oldTintoldCapacityoldTable。if(oldCapacityMAXIMUMCAPACITY){thresholdInteger。MAXVALUE;}创建新数组Entry〔〕newTablenewEntry〔newCapacity〕;元素重新求索引位置元素从旧数组移到新数组上transfer(newTable,initHashSeedAsNeeded(newCapacity));tablenewT设置新的负载数threshold(int)Math。min(newCapacityloadFactor,MAXIMUMCAPACITY1);}TransfersallentriesfromcurrenttabletonewTable。voidtransfer(Entry〔〕newTable,booleanrehash){intnewCapacitynewTable。for(EntryK,Ve:table){while(null!e){EntryK,Vnexte。if(rehash){e。hashnulle。key?0:hash(e。key);}intiindexFor(e。hash,newCapacity);e。nextnewTable〔i〕;newTable〔i〕e;}}}移除Removesthemappingforthespecifiedkeyfromthismapifpresent。paramkeykeywhosemappingistoberemovedfromthemapreturnthepreviousvalueassociatedwithttkeytt,orttnullttiftherewasnomappingforttkeytt。(Attnullttreturncanalsoindicatethatthemappreviouslyassociatedttnullttwithttkeytt。)publicVremove(Objectkey){移除key对应的元素并返回entryEntryK,VeremoveEntryForKey(key);return(enull?null:e。value);}RemovesandreturnstheentryassociatedwiththespecifiedkeyintheHashMap。ReturnsnulliftheHashMapcontainsnomappingforthiskey。finalEntryK,VremoveEntryForKey(Objectkey){if(size0){}inthash(keynull)?0:hash(key);根据元素hash值求索引intiindexFor(hash,table。length);桶的首个节点EntryK,Vprevtable〔i〕;EntryK,Vwhile(e!null){EntryK,Vnexte。O判断元素是否和移除的元素key和hash值相同,相同则进行移除操作if(e。hashhash((ke。key)key(key!nullkey。equals(k)))){modC如果桶的首个元素和被移除的元素相同则将next置为桶的首个元素if(preve)table〔i〕elseprev。e。recordRemoval(this);}}}get函数(根据key获取entry)publicVget(Objectkey){if(keynull)returngetForNullKey();EntryK,VentrygetEntry(key);returnnullentry?null:entry。getValue();}finalEntryK,VgetEntry(Objectkey){if(size0){}inthash(keynull)?0:hash(key);遍历索引上的链表for(EntryK,Vetable〔indexFor(hash,table。length)〕;e!ee。next){Ohash值相同key也相等则返回entry元素if(e。hashhash((ke。key)key(key!nullkey。equals(k))))}}缩容 无JDK1。8hashMap1。8hashMap变量初始容量16负载因子0。75数组节点类型是NodeK,VTREEIFYTHRESHOLD链表节点数大于TREEIFYTHRESHOLD转变为红黑树image20230202205319483put函数过程ImplementsMap。putandrelatedmethods。paramhashhashforkeyparamkeythekeyparamvaluethevaluetoputparamonlyIfAbsentiftrue,dontchangeexistingvalueparamevictiffalse,thetableisincreationmode。returnpreviousvalue,ornullifnonefinalVputVal(inthash,Kkey,Vvalue,booleanonlyIfAbsent,booleanevict){NodeK,V〔〕NodeK,Vp;intn,i;数组为空或长度为0初始化数组if((tabtable)null(ntab。length)0)n(tabresize())。索引位置的桶为空创建节点if((ptab〔i(n1)hash〕)null)tab〔i〕newNode(hash,key,value,null);else{NodeK,Ve;Kk;如果put的元素为桶的首个节点,e赋值if(p。hashhash((kp。key)key(key!nullkey。equals(k))))如果是红黑树将元素插入elseif(pinstanceofTreeNode)e((TreeNodeK,V)p)。putTreeVal(this,tab,hash,key,value);else{for(intbinCount0;;binCount){if((ep。next)null){p。nextnewNode(hash,key,value,null);链表长度大于TREEIFYTHRESHOLD链表转红黑树if(binCountTREEIFYTHRESHOLD1)1for1sttreeifyBin(tab,hash);}if(e。hashhash((ke。key)key(key!nullkey。equals(k))))}}统一处理,value值if(e!null){existingmappingforkeyVoldValuee。if(!onlyIfAbsentoldValuenull)e。afterNodeAccess(e);returnoldV}}modCsize大于负载容量时,扩容if(sizethreshold)扩容resize();afterNodeInsertion(evict);}hash函數staticfinalinthash(Objectkey){hashCode值与hashCode值右移16位做异或得出来的值高低位特征都有保留扰动效果更好return(keynull)?0:(hkey。hashCode())(h16);}扩容过程函数resizefinalNodeK,V〔〕resize(){NodeK,V〔〕oldTintoldCap(oldTabnull)?0:oldTab。intoldTintnewCap,newThr0;if(oldCap0){if(oldCapMAXIMUMCAPACITY){thresholdInteger。MAXVALUE;returnoldT}elseif((newCapoldCap1)MAXIMUMCAPACITYoldCapDEFAULTINITIALCAPACITY)newThroldThr1;doublethreshold}elseif(oldThr0)initialcapacitywasplacedinthresholdnewCapoldTelse{zeroinitialthresholdsignifiesusingdefaultsnewCapDEFAULTINITIALCAPACITY;newThr(int)(DEFAULTLOADFACTORDEFAULTINITIALCAPACITY);}if(newThr0){floatft(float)newCaploadFnewThr(newCapMAXIMUMCAPACITYft(float)MAXIMUMCAPACITY?(int)ft:Integer。MAXVALUE);}thresholdnewTSuppressWarnings({rawtypes,unchecked})NodeK,V〔〕newTab(NodeK,V〔〕)newNode〔newCap〕;tablenewTif(oldTab!null){for(intj0;joldCj){NodeK,Ve;if((eoldTab〔j〕)!null){oldTab〔j〕if(e。nextnull)newTab〔e。hash(newCap1)〕e;elseif(einstanceofTreeNode)((TreeNodeK,V)e)。split(this,newTab,j,oldCap);else{preserveorderNodeK,VloHeadnull,loTNodeK,VhiHeadnull,hiTNodeK,Vdo{nexte。if((e。hasholdCap)0){if(loTailnull)loHelseloTail。loT}else{if(hiTailnull)hiHelsehiTail。hiT}}while((enext)!null);if(loTail!null){loTail。newTab〔j〕loH}if(hiTail!null){hiTail。newTab〔joldCap〕hiH}}}}}returnnewT} 1。8的resize和1。7最大不同就是,元素重新求索引位置,不是单纯求hash(length1),而是看元素key的hash值在newCap1的高位是1还是0,如果是1元素索引位置oldCap,如果是0元素索引位置保持不变。get函数(和1。7类似)finalNodeK,VgetNode(inthash,Objectkey){NodeK,V〔〕NodeK,Vfirst,e;Kk;if((tabtable)!null(ntab。length)0(firsttab〔(n1)hash〕)!null){if(first。hashhashalwayscheckfirstnode((kfirst。key)key(key!nullkey。equals(k))))if((efirst。next)!null){if(firstinstanceofTreeNode)return((TreeNodeK,V)first)。getTreeNode(hash,key);do{if(e。hashhash((ke。key)key(key!nullkey。equals(k))))}while((ee。next)!null);}}}缩容 注意当链表长度小于6时,红黑树会变为链表面试考点JDK1。7,hashMap使用头插法为什么会造成死循环?voidtransfer(Entry〔〕newTable,booleanrehash){intnewCapacitynewTable。for(EntryK,Ve:table){while(null!e){第一处EntryK,Vnexte。if(rehash){e。hashnulle。key?0:hash(e。key);}intiindexFor(e。hash,newCapacity);e。nextnewTable〔i〕;newTable〔i〕e;}}} 原因是多线程并发扩容时,假设A,B两个线程,当A线程执行完第一处,时间片耗尽,线程B按照头插法,完成了完整扩容,此时链表相对于原来是逆序,A线程再继续顺序执行,会造成指针混乱,于是出现死循环。为什么容量是2的指数幂? 有两点,更好的得到新索引和搭配数组的length1可以使hash(length)hash(length1)作用相等更好的得到新索引,打个比方容量如果一开始时8,后面扩容成16从二进制上看只有最高位不同(length最高位后面都是0),所以在扩容的时候,需要重新用元素的hash值和length1求余实际上只有最高位不同,其他位不变,修改的数据少了,提高了代码的执行效率(1。7这样写,但是没利用到,但是1。8利用了这个特点)image20230201115404578如果扩容不是2的指数幂,那么扩容后的长度低位不会那么均匀,(试想下,如果低位有0的话,是不是每个hash值在这个位置都是0,大大提高了hash冲突的概率,是2的指数幂低位,length1低位全是1)也能更好的降低hash冲突的概率搭配数组的length1可以使hash(length)hash(length1)作用相等如果使用自定义对象作为hashMap的key为什么一定需要重写hashCode和equals方法? 因为hashMap插入元素,会用到hashCode计算hash值,如果没有重写的话,默认使用Object的实现,即对象的内存地址HashMapKeyk1newHashMapKey(1);HashMapKeyk2newHashMapKey(1);HashMapHashMapKey,StringmapnewHashMap();map。put(k1,test);System。out。println(map。get(k2):map。get(k2)); 上面的例子,如果沒有重写hashCode的话,k1和k2元素肯定不在数组的同一个位置上,因为hashCode使用的是内存地址,所以map。get(k2)null 那为什么要重写equals呢? 因为获取元素需要用到equals方法HashMapKeyk1newHashMapKey(1);HashMapKeyk2newHashMapKey(1);HashMapHashMapKey,StringmapnewHashMap();map。put(k1,test);System。out。println(map。get(k2):map。get(k2)); 还是上面的例子,k1和k2,不管有没有重写hashCode都有可能hash值相等(不同元素的hash可能相等),假设k1和k2的hash值相等,当根据key获取元素时,不但会判断元素的hash值还会判断key之间是否equals,如果没有重写equals方法(equals默认对象的内存地址),k1和k2的内存地址肯定不相同,所以map。get(k2)null 总结:使用自定义对象作为hashMap的key一定需要重写hashCode和equals方法,set集合比较,去重时(先比较hash,hash相同在比较equals),所以一样需要重写。HashMap线程不安全的体现:JDK1。7HashMap线程不安全体现在:死循环、数据丢失JDK1。8HashMap线程不安全体现在:数据覆盖思考问题 谈谈你理解的HashMap,讲讲其中的getput过程。1。8做了什么优化?是线程安全的嘛?不安全会导致哪些问题?如何解决?有没有线程安全的并发容器?ConcurrentHashMap是如何实现的?1。7、1。8实现有何不同?为什么这么做?引用 https:crossoverjie。top20180723javaseniorConcurrentHashMapHashMap 有问题,欢迎私信我哦