本篇文章内容较长,请耐心观看,相信对您理解HashMap的put方法会有所帮助。
在HashMap中put方法的源码如下:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
这个put方法并没有做任何的操作,直接把任务交给了putVal方法。我们接下来去看putVal方法。首先贴出putVal的声明:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)
hash:就是举例中的"a"的hash值
key:就是举例中的"a"
value:就是举例中的5
onlyIfAbsent:官方给的解释:@param onlyIfAbsent if true, don’t change existing value
,如果为true,将不能改变已经存在的值,举个例子:集合map中已经存在key=“a”,value=3,
如果onlyIfAbsent=true,在添加key=“a”,value=5,那么key="a"对应的值不会被改变,还是3。我们经常使用的put方法,此参数的值是false,说明put方法可以覆盖已经存在的值。
evict:参数用于LinkedHashMap中的尾部操作,在HashMap中并没有用到这个参数
接下来我们分析putVal的源码。
第一步:判断是否初始化了底层数组。
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
putVal第一段代码就是上面的代码,它首先将tab=table(这个table就是所说的底层的数组)并判断是否为null,同时判断tab的长度是否为0,只要两者有一个成立,就会进入n=(tab=resize()).length这句代码。在这一篇文章中我们分析了HashMap的构造方法,并没有一个构造方法对底层的数组进行初始化,所有的构造方法仅仅对加载因子或者扩容的阀门进行了初始化,底层数组初始化的工作放在了第一次调用put的时候,这个知识点大家一定要记住。因为这篇文章是介绍HashMap的put的,关于数组怎样进行初始化或者扩容的,我会有专门一篇文章详细讲解,这里就不再分精力阐述。只需记得put的第一步操作需要判断底层数组是否初始化了,如果没有初始化,就首先调用resize()进行初始化,如果已经初始化了,则继续执行下面的代码。通过这一步后,底层数组就变成了如下图示:
第二步:通过hash算出key应该在数组table的下标
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
1:(n-1)&hash:就是获取key应该在数组的下标
2:tab[(n-1)&hash]:就是获取此下标数组的值,然后赋值给p,而p是一个Node,如果p==null,说明此下标还没有任何的值,所以直接把新插入的元素放到此处。
3:tab[i] = newNode(hash, key, value, null):就是把新插入的key,value封装成Node节点,然后放到数组中。
例如,我们map.put(“a”,3)到HashMap中,通过计算应该放到下标为1的地方,如图所示
第三步:判断新插入的key和p.key是否相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
1:其中p=tab[(n-1)&hash]
2:判断新插入的key,和通过key计算的下标对应的key是否相等。
例如,我们map.put(“a”,5)到HashMap中,通过计算应该放到下标1的地方,而此时p=(“a”,3),那么p.hash=hash,p.key=key,所以会执行这一步。如图所示:
第四步:第三步不符合,接下来判断p是否为红黑树,如果是红黑树,那么按照红黑树的方式插入
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this,