建议用 IDEA打开源码配合来看
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
实际调用putVal方法:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
代码有点长, 我们一点一点来看:
首先,定义了4个变量 (Node<K,V>[] tab; Node<K,V> p; int n, i;),先不管他是什么意思,然后把一个 HashMap 的一个成员变量赋值给 tab,
由于我们第一次调用put方法, 所以成员变量此时的值为null,为null会调用一个 resize()方法,我们来看一下这个方法做了些什么。
我们调用不带参数的构造方法的时候,只对HashMap的负载因子进行了初始化,所以在这里的 threshold 的值为零,然后代码走到了 else 这里,将默认的初始化大小(16)赋值给newCap,然后计算出Map的容量达到多少之后需要扩容。这里将扩容的阀值赋值给成员变量 threshold 方便之后存取,然后new一个刚刚计算出数量的Note数组,再将Note数组赋值给成员变量table,此table就是HashMap具体存储数据的地方,接下来,oldTab依然为null,所以直接返回初始化好的Note数组-->> 这里将初始化好的数组的大小赋值给变量n,然后通过 算法(数组的长度 - 1 & key的hash值) 计算出数组的某个下标位置存储的元素赋值给 变量p,再判断这个元素是否为null,如果是null的话,创建一个Note,把note数据存储在计算好的Note数组的位置。 创建Note节点
note中存储了key的hash值,键值对,以及对下一个note节点的引用,由此可见,这是一个单向链表的结构
我们put数据的时候,会有两种情况,一是上面这种,数组下标节点没有数据,第二种是有值,接下来我们看第二种情况
这里又会有三种情况:
- 拿当前Map中的Note节点的hash值与要put进去的key的hash判断,相等的话,再比较Map中Note节点的key与传进来的key地址是否一致,如果一致,则将值覆盖,不一致就再继续进行key的equals比较,为true就覆盖,不然继续判断。
- 判断当前节点是否是一个红黑树结构,如果是的话,将数据存储到树节点中。
-
如果都不满足,则将数据添加到链表中。说的很简单,我们继续看一下代码:
p = 当前数组位置的元素
e = 当前数组位置的元素的链表的下一个
先判断当前节点链表是否有下一个,如果没有,则初始化Note节点,加入到链表中,再判断链表的长度,有没有达到转换树结构的阀值,达到了则将链表转换为树结构。
如果当前节点链表有下一个,判断节点数据的hash值与传进来的hash是否相等,相等则判断节点的key与传进来的key的地址值比较和equals比较,满足任何一条,就退出。不满足就继续迭代当前数组位置的链表,执行前面的逻辑。
最后把Map的Size加1, 判断是否需要扩容。
resize() 方法中还有许多逻辑判断,喜欢研究的自己去看吧,作者太菜,不爱看算法
网友评论