关于HashMap的实现原理的详细讲解,看这篇文章就够了。HashMap在Java集合中的重要性不亚于Volatile在并发编程中的重要性(可见性和顺序)。我会重点介绍以下9点:HashMap数据结构HashMap核心成员HashMap节点数组HashMap数据存储HashMap哈希函数Hash碰撞:链式哈希表HashMapget方法:哈希函数HashMapput方法为什么槽数一定要用2^n?HashMap的数据结构首先从数据结构的角度来看:HashMap是:数组+链表+红黑树(JDK1.8增加了红黑树部分)的数据结构,如下注:需要两道题这里需要澄清的是:数据底层究竟存储了什么?这种存储方式有什么优点?1、核心成员默认初始容量(数组默认大小):16,2的整数次方staticfinalintDEFAULT_INITIAL_CAPACITY=1<<4;最大容量staticfinalintMAXIMUM_CAPACITY=1<<30;默认负载因子staticfinalfloatDEFAULT_LOAD_FACTOR=0.75f;负载因子用于衡量HashMap的充实度,表示当map集合中存储的数据达到当前数组大小的75%时需要扩充链表到红黑树边界staticfinalintTREEIFY_THRESHOLD=8;红黑树移离链表边界staticfinalintUNTREEIFY_THRESHOLD=6;哈希桶数组瞬态Node[]表;实际存储的元素Numbertransientintsize;当map中的数据大于这个阈值时,会扩容intthresholdthreshold=table.length*loadFactor2.Nodearray从源码上看,HashMap类中有一个很重要的字段,就是Node[]table.即hashbucket数组,显然是Node的数组。staticclassNodeimplementsMap.Entry{finalinthash;//用于定位数组索引位置finalKkey;Vvalue;Nodeext;//链表的下一个Node节点Node(inthash,Kkey,Vvalue,Nodeext){this.hash=hash;this.key=key;this.value=value;this.next=next;}publicfinalKgetKey(){returnkey;}publicfinalVgetValue(){returnvalue;}publicfinalStringtoString(){returnkey+"="+value;}publicfinalinthashCode(){returnObjects.hashCode(key)^Objects.hashCode(value);}publicfinalVsetValue(VnewValue){VoldValue=value;value=newValue;returnoldValue;}publicfinalbooleanequals(Objecto){if(o==this)returntrue;if(oinstanceofMap.Entry){Map.Entrye=(Map.Entry)o;if(Objects.equals(key,e.getKey())&&Objects.equals(value,e.getValue()))returntrue;}returnfalse;}}Node是一个内部类HashMap,它实现了Map.Entry接口,本质上是一个映射(键值对)。HashMap数据存储1.哈希表存储HashMap使用哈希表来存储数据。哈希表(Hashtable,也叫哈希表)是一种根据键值(Keyvalue)直接访问的数据结构。只要要查找的值是key,就可以找到对应的值。哈希表实际上是数组的扩展,由数组演化而来。可以说没有数组就没有哈希表。2.哈希函数哈希表中的元素是由哈希函数决定的。以数据元素的关键字Key为参数,通过一定的函数关系(称为哈希函数)计算出的值就是该元素的存储地址。表示为:Addr=H(key),如下图所示:哈希表中哈希函数的设计非常重要,这也是构建哈希表过程中的关键问题之一。3.核心问题在构建哈希表之前,主要需要解决两个问题:构造一个合适的哈希函数,均匀性H(key)的值均匀分布在哈希表中冲突处理冲突:在哈希表中,不同的关键字值对应同一个存储位置的现象。4.哈希冲突:链式哈希表哈希表为了解决冲突可以使用地址法和链式地址法来解决问题。Java中的HashMap使用链地址方式。链地址法,简单来说就是数组和链表的组合,如下图所示:HashMap的哈希函数/***重新计算哈希值*/staticfinalinthash(Objectkey){inth;//h=key.hashCode()是第一步取hashCode值//h^(h>>>16)是第二步参与高位运算return(key==null)?0:(h=key.hashCode())^(h>>>16);}//计算数组slot(n-1)&hash对key进行hashCode运算得到一个32位的int值h,然后用h与h异或>>>16位。在JDK1.8的实现中,优化了高位运算的算法,通过hashCode()的高16位和低16位实现:(h=k.hashCode())^(h>>>16).这样做的好处是可以将hashcode的高位值和低位值混合进行异或运算,混合后将高位信息加入到低位信息中,从而使高位的-订单信息被伪装保存。这意味着哈希的高16位也参与了下标的计算。掺杂的元素越多,生成的哈希值的随机性就会增加,哈希碰撞就会减少。说明:^XOR:不同则为1,相同则为0>>>:无符号右移:右补0&运算:两位同时为“1”,结果为“1”,否则为0h&(table.length-1)获取对象的存储位,HashMap底层数组的长度永远是2的n次方。为什么槽数必须是2^n?1、为了让hash后的结果更加统一,如果slot的个数不是16,而是17,那么slot的计算公式就变成了:(17-1)&hash从上面可以看出,计算结果会大同小异。结果只剩下0和16两种,对hashmap来说简直是灾难。2、等价于长度模数当长度始终为2的n次方时,h&(length-1)运算等价于长度模数,即h%length,但&比%更高效。位运算的运算效率高于算术运算,因为算术运算还是会转化为位运算。最终目的是让散列的结果更加平均,减少散列冲突,提高hashmap的运行效率。分析HashMap的put方法:finalVputVal(inthash,Kkey,Vvalue,booleanonlyIfAbsent,booleanevict){Node
