1.HashMap源码
1.1 类Node代码
HashMap内部存储的单元是Node,Node类源码如下:
1.2 HashMap数据结构
HashMap数据结构是由Node数组及每个数组元素都是单向链表组成的,结构如下图:
1.3 HashMap get操作
get操作就是根据key找到value值。首先key算出hash值,然后调用getNode方法找到value。
已知HashMap table长度为n,n为2的m次方,即n=2^m, 然后通过hash算法(n-1)&hash算出下hash的二进制低m位作为table的index值,
从而索引到Node节点,判断此节点hash值和查找的key的值以及key是否相等,相等的话继续沿着此链表查找,知道查找到满足前两个条件
返回此Node节点,否则返回空。当节点数超过7个,链表转化为红黑树存储,查询就在此红黑树上进行。
1.4 HashMap put操作
HashMap put操作插入对应的key和value。同样先算出key的hash值,然后调用putVal方法。
putVal方法,同样首先根据hash(key)&(n-1)取得hash值二进制低m位找到index,这样的散列算法使key比较均匀的分布在各个桶里,找到
index索引到Node节点,如果为空,直接put在此节点,否则判断是否是红黑树,如果是则找到红黑树部分的节点则直接put,否则查找
链表中下一个Node节点的key值和hash等于插入的key和hash值的话直接更新Node节点的value值,否则找到Node节点为NULL的节点,
判断链表个数是否超过阈值7,超过链表转换为红黑树,不超过在链表new 新出的节点,然后判断HashMap的节点数是否大于阈值
(负载因子*table的长度),大于的话resize扩容,否则不扩容。
put方法示意图如下:
1.5 HashMap resize操作
HashMap resize方法首先计算出新的HashMap的容量newCap和新的threshold newThr。判断旧的map容量oldCap大于0并且大于最大容量,
则newThr设置为整形最大值,如果oldCap小于最大容量,则newCap扩大一倍,newThr也扩大一倍,当oldCap<=0&&oldThr>0更新newThr
等于oldThr,当oldCap<=0&&oldThr<=0时newCap设置为默认值,newThr设置为默认值(负载因子*16),当newThr=0时重新计算newThr的值。
计算完newCap和newThr后,开始HashMap的重新散列,因为Map的length(oldCap)发生变化,数据分布hash方法(length-1)&hash计算出来的索引值
有可能会变化,所以数据需要重新分布。方法是:先便利table数组,分别对数组元素对应每个链表执行rehash。如果链表只有一个节点,则只用计算
此节点对应的新的下标直接赋值即可,如果对应是红黑树,按照红黑树的逻辑去执行,如果是链表,则遍历每个链表,对应对一个节点重新计算出index,
jdk设计精巧体现在这里:因为hash方法(n-1)&hash,所以对于同一个key值,hash值一样,唯一的不同就是长度n,假设n是2的m次方,HashMap扩容之后
n扩大两倍,m加1,所以(n-1)&hash由之前的二进制取低m位变成了取低m+1位,前m位都是一样的,所以差别是第m+1位和1与计算后的结果,有两种情况
一是0,这种情况和扩容之前的取低m位一样,所以index也是一样,另一种情况是1,这种情况相当于2^m+扩容之前的index,第m+1位数字也可以用oldCap&hash
计算出等于0索引值不变,等于1索引值+2^m。所以链表就相应拆成两部分,一部分是索引等于index,一部分等于index+2^m。
PS:初始化长度:
因为Java规定了static final 类型为静态类变量 int 类型。int类型限制了该变量的长度为4个字节共32个二进制位,最高位是符号位。