的底层实现原理HashMap使用一个Node数组来存储键值对。每个键值对形成一个节点实体。Node类实际上是一个带有Next指针的单向链表结构,您可以连接到下一个节点实体。JDK1.8前后HashMap的区别在JDK1.8之前,数组+链表存储结构的缺点是hash函数很难让元素100%均匀分布,这会造成一个极大的极端可能性一个桶中JDK1.8后存在的元素个数:数组+链表+红黑树添加元素时:当链表长度大于8时,转为红黑树。删除元素扩容时,如上,当数量大于8时,也是以红黑树的形式存储,但是当数量较少,即数量小于6时,将红黑树转换回链表进行遍历和查找,使用红黑树。其时间复杂度为O(logn),便于性能提升。数据结构存储结构staticclassNodeimplementsMap.Entry{finalinthash;最后的K键;V值;下一个节点;Node(inthash,Kkey,Vvalue,Nodenext){this.hash=hash;this.key=键;this.value=值;这个.下一个=下一个;}publicfinalKgetKey(){返回密钥;}publicfinalVgetValue(){返回值;}publicfinalStringtoString(){返回键+“=”+值;}publicfinalinthashCode(){returnObjects.hashCode(key)^Objects.hashCode(value);}publicfinalVsetValue(VnewValue){VoldValue=value;价值=新价值;返回旧值;}publicfinalbooleanequals(Objecto){if(o==this)returntrue;if(oinstanceofMap.Entry){Map.Entry,?>e=(Map.Entry,?>)o;如果(Objects.equals(key,e.getKey())&&Objects.equals(value,e.getValue()))返回true;}返回假;}}//NodearraytransientNode[]table;//加载因子finalfloatloadFactor;//默认加载因子,当容量达到这个比例时,会自动扩容staticfinalfloatDEFAULT_LOAD_FACTOR=0.75f;//当数大于8时,链表转为红黑树形式的StorestaticfinalintTREEIFY_THRESHOLD=8;//当数小于6时,将红黑树黑树将被转换回链表。删除元素时removestaticfinalintUNTREEIFY_THRESHOLD=6;//PUT每次都做hashpublicVput(Kkey,Vvalue){returnputVal(hash(key),key,value,false,true);}//put函数核心算法finalVputVal(inthash,Kkey,Vvalue,booleanonlyIfAbsent,booleanevict){Node[]tab;节点p;诠释n,我;如果((tab=table)==null||(n=tab.length)==0)n=(tab=resize()).length;if((p=tab[i=(n-1)&hash])==null)//这里的n表示数组的长度//hashtab[i]=newNode(hash,key,value,null);else{节点e;Kk;if(p.hash==hash&&((k=p.key)==key||(key!=null&&key.equals(k))))e=p;elseif(pinstanceofTreeNode)e=((TreeNode)p).putTreeVal(this,tab,hash,key,value);else{for(intbinCount=0;;++binCount){if((e=p.next)==null){p.next=newNode(hash,key,value,null);if(binCount>=TREEIFY_THRESHOLD-1)//-1对于第一个treeifyBin(tab,hash);休息;}if(e.hash==hash&&((k=e.key)==key||(key!=null&&key.equals(k))))中断;p=e;}}if(e!=null){//键VoldValue=e.value的现有映射;如果(!onlyIfAbsent||oldValue==null)e.value=value;节点访问后(e);//空实现returnoldValue;}}++模数;//modCount是java集合中Fail-Fast的底层实现原理if(++size>threshold)//expansionresize();节点插入后(逐出);//空实现returnnull;}//允许LinkedHashMap后操作的回调voidafterNodeAccess(Nodep){}voidafterNodeInsertion(booleanevict){}voidafterNodeRemoval(Nodep){}想想java中的快速失败collections:modCount//?Fastfailure是Java集合的一种错误检测机制。当多个线程对集合进行结构改变时,可能会出现fail-fast//例如:假设有两个线程(线程1,线程2),线程1通过Iterator遍历集合A中的元素,线程2修改集合A在某个时刻的结构(是对结构的修改,不是简单的修改集合元素的内容),那么此时程序可能会抛出ConcurrentModificationException异常,导致fast-fail快速失败。HashMap中遍历算法如下:finalclassKeySetextendsAbstractSet{publicfinalintsize(){returnsize;}publicfinalvoidclear(){HashMap.this.clear();}publicfinalIteratoriterator(){returnnewKeyIterator();}publicfinalbooleancontains(Objecto){returncontainsKey(o);}publicfinalbooleanremove(Objectkey){returnremoveNode(hash(key),key,null,false,true)!=null;}publicfinalSpliteratorspliterator(){returnnewKeySpliterator<>(HashMap.this,0,-1,0,0);}publicfinalvoidforEach(Consumeraction){Node[]tab;if(action==null)thrownewNullPointerException();if(size>0&&(tab=table)!=null){intmc=modCount;for(inti=0;ie=tab[i];e!=null;e=e.next)action.accept(电子钥匙);}if(modCount!=mc)//抛出异常,Fail-FastthrownewConcurrentModificationException();}}}优化JDK1.8中的hash算法,通过hashCode()的高16位或者低16位实现:(h=k.hashCode())^(h>>>16),主要来自于从速度、效率和质量的角度考虑,减少系统开销,不会造成高层不参与下标的计算,导致碰撞//干扰函数:促进元素位置均匀分布,降低碰撞概率staticfinalinthash(Object键){inth;//ifkey==nullreturnvalueis0//ifkey!=null//先计算出key的hashcode值赋值给h,//然后将h无符号右移16位得到最终的hash值返回(键==null)?0:(h=key.hashCode())^(h>>>16);}hash的具体实现过程如下图所示:hash计算过程及putval函数key.hashCode()的应用;返回的哈希值是哈希码,假设它是随机生成的一个值。n表示初始化数组的长度为16。&(按位与运算):运算规则:当相同的二进制数位都为1时,结果为1,否则为0。^(按位异或运算):运算规则:在相同的二进制数位上,数相同则结果为0,不同则结果为1。高16bit不变,低16bit与高16bit异或(将得到的hashCode转为32位二进制,前16位和后16位与低16bit与高16bit异或).为什么要这样实施?如果n是数组的长度很小,假设是16,那么n-1就是1111,这样的值和hashCode直接进行按位AND运算,实际上只用到了hash值的后4位。如果哈希值的高位变化很大,而低位变化很小,很容易造成哈希冲突,所以这里使用高位和低位来解决这个问题。降低了Hash冲突的概率为什么要使用XOR运算符来保证只要对象的hashCode的32位值中的一位发生变化,整个hash()的返回值就会发生变化。尽可能减少碰撞。工作原理存储对象时,将K/V键值传递给put()方法:调用hash(K)方法计算K的哈希值,然后结合数组长度计算数组下标;调整数组的大小(当容器中的元素个数大于capacity*loadfactor时,容器会扩容并调整大小为2n);散列冲突i.如果HashMap中不存在K的哈希值,则进行插入,存在则发生碰撞;二.如果哈希值K的值存在于HashMap中,且两者都等于返回true,则更新键值对;三.如果K的hash值存在于HashMap中,且两者都等于returnfalse,则插入链表的尾部(尾部插入法)或者红黑树中(树的添加方式)。(JDK1.7之前使用头插入法、JDK1.8使用尾插入法)//get实现publicVget(Objectkey){Nodee;返回(e=getNode(哈希(键),键))==空?null:e.value;}/***实现Map.get和相关方法**@paramhashhashforkey*@paramkeythekey*@returnfinalNodegetNode(inthash,Objectkey){节点[]选项卡;节点先,e;诠释n;Kk;if((tab=table)!=null&&(n=tab.length)>0&&(first=tab[(n-1)&hash])!=null){if(first.hash==hash&&//始终检查第一个节点((k=first.key)==key||(key!=null&&key.equals(k))))首先返回;if((e=first.next)!=null){if(firstinstanceofTreeNode)return((TreeNode)first).getTreeNode(hash,key);}做{if(e.hash==hash&&((k=e.key)==key||(key!=null&&key.equals(k))))返回e;}while((e=e.next)!=null);}}returnnull;}思考问题?问:默认初始化大小是多少?为什么有这么多?为什么大小都是2的幂?A:hash运算的过程其实就是对目标元素的Key进行hashcode,然后对Map的容量取模。JDK工程师为了提高取模的效率,使用位运算代替取模运算,这就要求Map的容量必须是2的n次方。为什么HashMap的容量是2的n次方,这跟计算有着千丝万缕的关系此putval方法中的(n-1)&hash方法。符号&是按位与计算。这是位操作,可以直接用电脑操作,效率特别高。按位与&&的计算方式是只有对应位置的数据为1时,运算结果也为1。当HashMap的容量为2的n次方时,(n-1)的二进制为1111111***111的形式,这样当与添加元素的hash值进行位运算时,可以(足够hash),使得添加的元素均匀分布在HashMap的各个位置,hash碰撞减少。Q:HashMap是如何有效减少碰撞的?A:扰动函数:促进元素位置均匀分布,降低碰撞概率,使用final对象,采用合适的equals()和hashCode()方法