HashMap 1.7 的头插法(Head Insertion) 是其与 1.8 尾插法区别
HashMap 1.7 的头插法(Head Insertion) 是其与 1.8 版本最显著的区别之一。
核心逻辑是:新插入的节点永远放在链表的头部(即数组桶位的第一个位置),而不是尾部。
- 为什么叫“头插法”?
在 HashMap 1.7 中,当发生哈希冲突(多个 key 映射到同一个数组索引)时,新来的 Entry 会直接插在链表的最前面,原来的链表整体后移。
图解变化过程:
假设数组索引 i 处已经有一个链表:A -> B -> null
1.插入新节点 C
2.C 的 next 指向 A
3.数组索引 i 指向 C
结果变成:C -> A -> B -> null - 核心代码实现(JDK 1.7 源码简化版)
在 HashMap 的 addEntry 或 transfer(扩容时)方法中,逻辑如下:
java
void addEntry(int hash, K key, V value, int bucketIndex)
{
// 1. 获取当前桶位(数组索引)的头节点
Entry<K,V> e = table[bucketIndex];
// 2. 创建新节点,并将新节点的 next 指向原来的头节点 e
// 这就是“头插”的关键一步:新节点把旧链表串在自己后面
table[bucketIndex] =
new Entry
<>(hash, key, value, e);
// 3. 修改 size 等统计信息
size++;
}
关键点解析:
new Entry<>(hash, key, value, e):构造函数的第四个参数就是 next。
这里直接把当前的 table[bucketIndex](旧头节点)传给了新节点的 next。
然后把 table[bucketIndex] 更新为新节点。
时间复杂度 O(1):不需要遍历链表找到尾部,直接修改指针即可,效率极高。
- 头插法 vs 尾插法(1.8)对比

- 致命缺陷:多线程下的死循环问题
这是面试中最常问的点。HashMap 1.7 在多线程扩容(resize)时,使用头插法会导致链表形成环形结构,进而导致 CPU 100% 死循环。
死循环成因简述:
线程 A 和线程 B 同时触发扩容。
扩容时需要将旧链表的数据重新哈希到新数组。
由于是头插法,链表顺序会反转。
在并发环境下,两个线程同时操作同一个链表节点,可能导致:
节点 A 的 next 指向 B
节点 B 的 next 又指回 A
形成 A -> B -> A 的环。
当后续有线程进行 get() 操作遍历这个链表时,会陷入无限循环 while(e != null),导致 CPU 飙升至 100%。
JDK 1.8 的改进:
改为尾插法:保持链表原有顺序,不会出现因反转导致的环路。
虽然 1.8 依然不是线程安全的(可能数据覆盖),但至少不会死循环了。
5. 总结
实现原理:新节点 next 指向旧头节点,数组引用指向新节点。
优点:插入速度快,O(1),无需遍历。
缺点:
破坏了插入顺序(LIFO)。
多线程扩容时极易产生死循环(这是 1.7 被弃用的主要原因之一)。
一句话记忆:1.7 头插快但有毒(死循环),1.8 尾插稳且有序。
更多推荐




所有评论(0)