HashMap put(),resize()解析
查看的源码是jdk1.8版本,这里对jdk1.7的HashMap不详细讨论
map常量讲解:
首先,先看看HashMap定义的常量
前三个分别是默认容量=16,最大容量=2^30,默认的负载因子=0.75。当map里的节点大小大于总容量*负载因子时,会进行扩容。
后三个分别是树化长度=8,退化长度=6,以及最小的树化容量=64。jdk1.8中,HashMap的实现是数组加链表加红黑树,当链表长度大于等于8,进行树化,但是首先还要Map的容量大于最小树化容量64,才可以进行树化。
put函数解析
调用put函数,会先通过hash(key)计算key的hash值,onlyIfAbsent的值是指是否允许修改已经存在的key的value值。默认是同意的。代码逻辑如下,传过来的值是false,所以默认就是会替换值。
之后进入putVal函数里面了
如果我们调用newHashMap<>()函数创建Map,一开始的时候,表是没有创建的,所以会先扩容,容量为16。
之后,计算下标(n-1)& hash,计算完了就插入到table表中,如果对应的下标一个节点没有,就直接创建。如果有节点,就遍历判断是不是已经存在key值,如果存在要进行值的替换。如果发现已经是树节点了,就进入树的添加环节。如果遍历到尾部发现还是没有相同的key值,就在尾部添加节点。
当执行完插入过程后,就到了判断是否需要扩容的环节了。先插入,后判断是否需要扩容。
threshold属性的计算是容量*负载因子。可以将threshold理解为扩容的阈值
resize函数解析
resize函数做了两件事情,一是计算新的容量,和新的阈值大小。二是将旧的节点移到新的表。
计算新容量,新阈值,分为三种情况,一是空参创建,二是指定了容量创建,三是已经有表,正常的扩容。
在指定容量创建的时候,也是不创建表的,把容量的数值存放在了threshold属性里。
如果已经存在表了,那就进行正常的扩容过程,容量2,阈值2。如果长度已经大于等于最大容量,就不扩容。
最后就是将旧表中的节点放到新表(table是Node节点类型的数组)中,在这里其实1.8有了一个巧妙的计算下标的方式,如下图
因为length,其实就是容量是保证了其值是2^n的,所以length-1&hash计算下标不会导致下标越界。同时,新的length(新容量)其实就是旧容量左移一位。所以可以巧妙的计算新下标的值要么是原来的,要么是原来的加上旧容量长度。