HashMap 的 put 方法简单流程
概述
看了一些文章后,自己整理的 HashMap 的 put 方法的大体流程。看的时候,有的细节先不用细看,不然容易头大。文字版省略了一点细节。整个流程梳理清楚后,再回头看看细节。
文字版流程
- 通过经典的(n-1)& hash (先不纠结为啥这么计算)获取 key 所在的数组下标,然后从数组中获取节点并赋值给变量 p。
(p = tab[i = (n - 1) & hash])
- 如果 p 为 null,直接将传来的 key 和 value 新增。
newNode(hash, key, value, null)
- 如果 p 不为 null,满足以下两个条件,则获取当前的节点并赋值给变量 e,并在第9步将旧值覆盖。
p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))
- p 的 key 的 hash 和传来的 hash(其实就是传来的 key 计算出来的)相等。
- p 的 key 的内存地址值和传来的 key 的内存地址值是否相等;或者如果内存地址值不相等,那通过 equal 计算的值是否相等。
- 到这一步说明传来的 key 与 p 的 key 不相等。如果当前位置是一个红黑树则直接插入到红黑树里。
p instanceof TreeNode
- 如果 key 不相等,还不是红黑树,那就是链表了。遍历链表。
- 链表节点为 null,就是遍历到最后一个了,说明之前遍历的 key 和传来的 key 都不相等,直接插入当前值。如果插入之后节点数 > 8, 转换为红黑树。
(e = p.next) == null
- 还是之前的判断key是否相等。相等就获取当前的节点并赋值给变量e,第9步会覆盖旧值。
p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))
- 进行下一次遍历
p = e
(这个先不要深入看,整体逻辑搞清楚,再看为啥 p = e) - 如果获取到的节点不为null(第3步和第7步会获取到节点e),就覆盖旧值。
e != null
10.节点超过阈值,扩容。
resize()
代码版流程
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;
// 1.根据 (n - 1) & hash 计算出数组下标,得到 Node p
if ((p = tab[i = (n - 1) & hash]) == null)
// 2.p == null ,此位置没有Node,直接新增
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 3.如果 p的hash等于传来的hash而且p的key与传来的key相同,说明key相同,只变更value即可。
// 3.如果 p的hash等于传来的hash而且p的key与传来的key字面相同,也说明key相同,只变更value即可。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 4.如果是红黑树,直接插入
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 5.如果是链表,循环判断
for (int binCount = 0; ; ++binCount) {
// 6.遍历到节点为null,就是最后一个节点,前面的节点的key都和传来的不相等
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 7.如果key相等,获取当前的节点,下面有一个if会覆盖旧值
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
// 8.进行下一次遍历
p = e;
}
}
// 9.e不为null,说明有key和传来的key相等了,覆盖旧值就行
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 好玩的钩子方法
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 10.如果节点个数超过阈值,扩容
if (++size > threshold)
resize();
// 好玩的钩子方法
afterNodeInsertion(evict);
return null;
}
欢迎讨论~