// 冲突是指要存储添加数据位置上已经有数据了
// hash是key的哈希值,key|value是要被put的键值对
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
// tab:存储数据的节点数组,p代表当前元素,n数组的长度,i代表计算的键值对的存储位置
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 第一次添加元素进行扩容16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 判断这个位置上i有没有数据:没有数据直接添加到这个位置上
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {// 这个位置上有数据
// e是临时节点变量,k也是临时key,用于记录下一个元素.
Node<K,V> e; K k;
// key哈希值和冲突key一样,且key值都相等==说明key已存在则将替换这个位置上的第一个元素
if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
e = p;// 这个位置上的第一个元素赋给e
else if (p instanceof TreeNode)
// 如果是红黑树,则按红黑树规则添加元素
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 如果是链表且链表第一个元素和我们要添加的元素key不同,依次判断当前元素的下一个节点是否有元素:
for (int binCount = 0; ; ++binCount) {
// 没有元素则直接添加到当前元素后面
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 添加完判断是否超过8个元素
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);// 链表超过8个元素则转成红黑树存储(可提高查询效率)
break;
}
// 要添加的元素和链表上的元素依次比较,key一旦存在则替换
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;
// onlyIfAbsent为false,
if (!onlyIfAbsent || oldValue == null)
// 替换冲突位置上元素的值value
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
细心总结:
put的时候会先判断数组是否为空,为空则表示是第一次put进行resize扩容为16;然后通过哈希算法计算键值对在数组中的存储位置,判断这个位置上是否有数据:
-
①没有数据,则插入到这个位置上 --情况1
-
②有数据,key和这个位置上所有数据的key依次比较哈希值,判断是否有相等的:
-
都没有相等的,说明key不存在,如果是红黑树则添加到红黑树中;如果是链表则添加到末尾并判断节点数据个数如果大于8则转成红黑树存储 --情况2
-
有相等的,调用key的equal比较:
-
返回true,说明key已存在,替换掉value
-
返回false,继续第②步
-
-
这里也说明了一个情况,因为equal和hashCode方法具有联动性,equal和hashCode方法要同时重写,同时重写的规则要满足equal为true,哈希值一定也要相同;哈希值相同,equal不一定为true。
通俗理解版总结:
put的时候会先判断数组是否为空,为空则表示是第一次put进行resize扩容为16;然后通过哈希算法计算键值对在数组中的存储位置,判断这个位置上是否有数据:
-
①没有数据,则插入到这个位置上 --情况1
-
②有数据,这个位置上如果用红黑树存储则按红黑树规则添加元素;如果用链表存储,key依次和这个链表上的元素进行比较:
-
如果key存在,则替换value
-
如果key不存在,则在链表末尾添加元素(尾插法),每添加完都要判断当前位置上的元素个数是否大于8?转为红黑树存储:不做操作。
-
比较都是先比较哈希值是否相同,相同才调equal比较key是否相同。