一、put() 方法核心流程
1. 基本执行流程
-
计算键的哈希值:
hash = hash(key) -
确定桶位置:
index = (n - 1) & hash(n为数组长度) -
处理桶位置元素:
-
桶为空:直接插入新节点
-
桶不为空:处理哈希冲突
-
2. 哈希冲突处理详细步骤
当发生哈希冲突时(即计算的桶位置已有元素),HashMap 的处理逻辑如下:
// 当前桶的第一个节点
Node<K,V> p = tab[index];
// 情况1:检查第一个节点是否匹配
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) {
// key完全相同,准备覆盖值
e = p;
}
// 情况2:如果是树节点
else if (p instanceof TreeNode) {
// 调用红黑树的插入方法
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
}
// 情况3:链表遍历
else {
for (int binCount = 0; ; ++binCount) {
// 到达链表尾部
if ((e = p.next) == null) {
// 在尾部插入新节点(JDK8尾插法)
p.next = newNode(hash, key, value, null);
// 检查是否需要树化(链表长度≥8)
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
// 检查当前节点是否匹配
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
break; // 找到相同key的节点
}
p = e; // 继续遍历下一个节点
}
}
// 处理值覆盖
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value; // 覆盖旧值
afterNodeAccess(e);
return oldValue; // 返回旧值
}
二、链表遍历关键细节
1. 节点比较顺序
-
先比较哈希值:快速筛选,哈希值不同直接跳过
-
if (e.hash == hash)
-
-
再比较引用地址:== 比较,处理相同对象的情况
-
(k = e.key) == key
-
-
最后比较内容:equals() 方法比较
-
key != null && key.equals(k)
-
2. 插入方式演变
| JDK版本 | 插入方式 | 特点 |
|---|---|---|
| JDK7 | 头插法 | 新节点插入链表头部,多线程扩容可能导致死链 |
| JDK8+ | 尾插法 | 新节点追加到链表尾部,解决死链问题 |
3. 树化条件检查
在链表遍历时会同时计数(binCount),当满足:
-
链表长度 ≥ TREEIFY_THRESHOLD(默认8)
-
桶数组长度 ≥ MIN_TREEIFY_CAPACITY(默认64)
会将链表转换为红黑树,否则优先进行扩容。
三、完整示例分析
假设有以下操作:
Map<String, Integer> map = new HashMap<>();
map.put("A", 1); // 哈希假设计算到桶5
map.put("B", 2); // 哈希也到桶5(冲突)
执行 map.put("C", 3) 且 "C" 也哈希到桶5:
-
定位到桶5,发现已有链表 A → B
-
开始遍历:
-
比较A:不匹配(hash或equals)
-
比较B:不匹配
-
到达链表尾部(B.next为null)
-
-
执行插入:
B.next = new Node("C", 3); // 链表变为 A → B → C -
检查长度:
-
当前链表长度=3(<8),不触发树化
-
-
完成插入
四、关键注意事项
-
性能影响因素:
-
哈希函数的质量直接影响冲突概率
-
链表过长会降低查询性能(O(n))
-
-
正确实现键对象:
class MyKey { private String id; @Override public int hashCode() { return id.hashCode(); // 保证相同id返回相同hash } @Override public boolean equals(Object o) { // 必须与hashCode()保持一致 return o instanceof MyKey && ((MyKey)o).id.equals(this.id); } } -
并发问题警示:
// 不安全的操作 HashMap<String, Integer> unsafeMap = new HashMap<>(); // 并发put可能导致数据丢失或死循环(JDK7) // 解决方案 ConcurrentHashMap<String, Integer> safeMap = new ConcurrentHashMap<>();
理解 HashMap 的 put 机制对于编写高效、正确的 Java 程序至关重要,特别是在处理大规模数据或高并发场景时。

1705

被折叠的 条评论
为什么被折叠?



