你好,我是风一样的树懒,一个工作十多年的后端开发,曾就职京东、阿里等多家互联网头部企业。
文章可能会比较长,主要解析的非常详解,或涉及一些底层知识,供面试高阶难度用。可以根据自己实际理解情况合理取舍阅读
在 Java 1.7 和 1.8 版本中,HashMap 主要的区别集中在 底层数据结构、解决哈希冲突的方式、扩容机制 等方面。下面是详细的对比分析:
特性 | JDK 1.7 HashMap | JDK 1.8 HashMap |
---|---|---|
底层数据结构 | 数组 + 链表(Entry<K, V>) | 数组 + 链表 + 红黑树(Node<K, V> + TreeNode<K, V>) |
哈希冲突解决 | 链地址法(拉链法) | 链表 + 红黑树(当链表过长时转为红黑树) |
扩容机制 | 先扩容,再迁移(rehash) | 先插入新数据,再扩容 |
链表插入顺序 | 头插法(可能导致死循环) | 尾插法(避免死循环) |
红黑树优化 | ❌ 不支持红黑树 | ✅ 当链表长度 >= 8 时,链表转换为红黑树 |
resize() 触发条件 | 数组元素数量 >= 容量 * 负载因子(默认 0.75) | 数组元素数量 >= 容量 * 负载因子 |
数组 (Entry<K, V>[] table):存储 Entry 对象(键值对)。
链表 (Entry<K, V>):当多个键的哈希值相同时,它们存储在同一个链表中。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next; // 指向下一个节点
final int hash;
}
哈希冲突解决方式:使用 拉链法(即在数组的相同索引位置用 链表 连接多个 Entry)。
链表插入方式:头插法,新节点插入链表头部。
仍然是 数组 Node<K, V>[] table 存储数据。
哈希冲突处理优化:
当链表长度 ≥ 8 时,转换为红黑树,提高查找效率(O(n) → O(log n))。
当链表长度 ≤ 6 时,恢复为链表,节省空间。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; // 仍然是拉链法
}
仅使用 链表 解决冲突,哈希冲突严重时,链表会变得很长,查找效率 O(n)。
链表采用头插法(新元素插入链表头部)。
优化:引入红黑树
当链表长度 ≥ 8 时,自动转换为 红黑树,查找效率 O(log n)。
当链表长度 ≤ 6 时,恢复为 链表。
链表采用尾插法(避免 JDK 1.7 可能出现的死循环问题)。
触发条件:元素个数 ≥ 数组容量 * 负载因子(默认 0.75)。
扩容过程:
新建数组(2 倍大小)。
迁移数据(rehash):遍历旧数组,将元素重新计算 hash & (newCapacity - 1) 进行存放。
可能发生死循环(rehash 时 Entry 采用 头插法,可能导致环形链表)。
触发条件 不变。
优化点:
先插入新数据,再扩容(提高写入性能)。
避免 rehash 计算:
JDK 1.7 rehash 需要 hash % newCapacity 计算新索引。
JDK 1.8 通过 hash & (newCapacity - 1) 计算(更高效)。
扩容示例
// JDK 1.8
Node<K, V> oldNode = table[i];
table[i] = oldNode.hash & oldCapacity == 0 ? oldNode : newTable[i];
减少数据迁移,优化性能。
新元素总是插入链表头部。
resize() 迁移数据时,如果 链表反向,可能导致 死循环。
newEntry.next = table[index];
table[index] = newEntry;
新元素插入链表尾部,保证链表顺序稳定。
resize() 过程不会出现 死循环。
Node<K,V> p = table[i];
while (p.next != null) {
p = p.next;
}
p.next = newNode;
无红黑树,大量哈希冲突会导致 链表过长,查找效率 O(n)。
链表长度 ≥ 8 时转换为红黑树:查找性能从 O(n) → O(log n)。
链表长度 ≤ 6 时恢复为链表(节省空间)。
红黑树插入
if (binCount >= TREEIFY_THRESHOLD) {
treeifyBin(tab, hash);
}
JDK 1.7 HashMap | JDK 1.8 HashMap |
---|---|
适用于小规模数据存储 | 适用于大规模数据存储 |
哈希冲突多时,性能降低 | 哈希冲突优化,查找效率更高 |
扩容时链表可能会死循环 | 尾插法 + 红黑树,避免死循环 |
特性 | JDK 1.7 HashMap | JDK 1.8 HashMap |
---|---|---|
底层结构 | 数组 + 链表 | 数组 + 链表 + 红黑树 |
哈希冲突解决 | 仅链表(O(n)) | 链表(O(n))+ 红黑树(O(log n)) |
扩容机制 | 先扩容,再迁移(rehash) | 先插入,再扩容 |
链表插入方式 | 头插法(导致死循环) | 尾插法(避免死循环) |
查询性能 | O(n)(链表遍历) | O(log n)(红黑树) |
JDK 1.8 HashMap 优势:
红黑树优化,查找性能更高。
扩容优化,避免 rehash 计算。
尾插法,避免死循环问题。
JDK 1.7 HashMap 适用于小规模数据,JDK 1.8 HashMap 更适用于高并发和大数据场景。
下期我们分享冲突及线程安全问题
今天的内容就分享到这儿,喜欢的朋友可以关注,点赞。有什么不足的地方欢迎留言指出,您的关注是我前进的动力!