JDK 1.7 vs JDK 1.8 中 HashMap 的区别

你好,我是风一样的树懒,一个工作十多年的后端开发,曾就职京东、阿里等多家互联网头部企业。

点击下方👇关注公众号,带你一起复习后端技术,看看面试考点,补充积累技术知识,每天都为面试准备积累

文章可能会比较长,主要解析的非常详解,或涉及一些底层知识,供面试高阶难度用。可以根据自己实际理解情况合理取舍阅读


在 Java 1.7 和 1.8 版本中,HashMap 主要的区别集中在 底层数据结构、解决哈希冲突的方式、扩容机制 等方面。下面是详细的对比分析:

01
JDK 1.7 vs JDK 1.8 HashMap 的核心区别


特性JDK 1.7 HashMapJDK 1.8 HashMap
底层数据结构数组 + 链表(Entry<K, V>)数组 + 链表 + 红黑树(Node<K, V> + TreeNode<K, V>)
哈希冲突解决链地址法(拉链法)链表 + 红黑树(当链表过长时转为红黑树)
扩容机制先扩容,再迁移(rehash)先插入新数据,再扩容
链表插入顺序头插法(可能导致死循环)尾插法(避免死循环)
红黑树优化❌ 不支持红黑树✅ 当链表长度 >= 8 时,链表转换为红黑树
resize() 触发条件数组元素数量 >= 容量 * 负载因子(默认 0.75)数组元素数量 >= 容量 * 负载因子


02
HashMap 1.7 vs 1.8 详细解析


2.1 底层数据结构

JDK 1.7

HashMap 的底层数据结构是 数组 + 链表,其中:
  • 数组 (Entry<K, V>[] table):存储 Entry 对象(键值对)。

  • 链表 (Entry<K, V>):当多个键的哈希值相同时,它们存储在同一个链表中。







static class Entry<K,Vimplements Map.Entry<K,V> {    final K key;    V value;    Entry<K,V> next; // 指向下一个节点    final int hash;}


哈希冲突解决方式:使用 拉链法(即在数组的相同索引位置用 链表 连接多个 Entry)。

链表插入方式:头插法,新节点插入链表头部。

JDK 1.8

结构变为 数组 + 链表 + 红黑树:
  • 仍然是 数组 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; // 仍然是拉链法}


2.2 解决哈希冲突方式

JDK 1.7

  • 仅使用 链表 解决冲突,哈希冲突严重时,链表会变得很长,查找效率 O(n)。

  • 链表采用头插法(新元素插入链表头部)。

JDK 1.8

优化:引入红黑树

  • 当链表长度 ≥ 8 时,自动转换为 红黑树,查找效率 O(log n)。

  • 当链表长度 ≤ 6 时,恢复为 链表。

链表采用尾插法(避免 JDK 1.7 可能出现的死循环问题)。


2.3 扩容机制

JDK 1.7

触发条件:元素个数 ≥ 数组容量 * 负载因子(默认 0.75)。

扩容过程:

  • 新建数组(2 倍大小)。

  • 迁移数据(rehash):遍历旧数组,将元素重新计算 hash & (newCapacity - 1) 进行存放。

  • 可能发生死循环(rehash 时 Entry 采用 头插法,可能导致环形链表)。

JDK 1.8

触发条件 不变。

优化点:

  • 先插入新数据,再扩容(提高写入性能)。

  • 避免 rehash 计算:

    • JDK 1.7 rehash 需要 hash % newCapacity 计算新索引。

    • JDK 1.8 通过 hash & (newCapacity - 1) 计算(更高效)。


扩容示例

// JDK 1.8Node<K, V> oldNode = table[i];table[i] = oldNode.hash & oldCapacity == 0 ? oldNode : newTable[i];
  • 减少数据迁移,优化性能。


2.4 头插法 vs 尾插法

JDK 1.7

Entry<K, V> 头插法:
  • 新元素总是插入链表头部。

  • resize() 迁移数据时,如果 链表反向,可能导致 死循环。

newEntry.next = table[index];table[index] = newEntry;

JDK 1.8

Node<K, V> 尾插法:
  • 新元素插入链表尾部,保证链表顺序稳定。

  • resize() 过程不会出现 死循环。

Node<K,V> p = table[i];while (p.next != null) {    p = p.next;}p.next = newNode;


2.5 红黑树优化

JDK 1.7

  • 无红黑树,大量哈希冲突会导致 链表过长,查找效率 O(n)。

JDK 1.8

  • 链表长度 ≥ 8 时转换为红黑树:查找性能从 O(n) → O(log n)。

  • 链表长度 ≤ 6 时恢复为链表(节省空间)。

红黑树插入

if (binCount >= TREEIFY_THRESHOLD) {    treeifyBin(tab, hash);}


03
适用场景对比


JDK 1.7 HashMapJDK 1.8 HashMap
适用于小规模数据存储适用于大规模数据存储
哈希冲突多时,性能降低哈希冲突优化,查找效率更高
扩容时链表可能会死循环尾插法 + 红黑树,避免死循环


04
总结


特性JDK 1.7 HashMapJDK 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 更适用于高并发和大数据场景。


下期我们分享冲突及线程安全问题

今天的内容就分享到这儿,喜欢的朋友可以关注,点赞。有什么不足的地方欢迎留言指出,您的关注是我前进的动力!

END


扫码关注

一起积累后端知识
不积跬步,无以至千里
不积小流,无以成江海

喜欢此内容的人还喜欢

《Java面试题指南》回归啦~


一个阿里二面面试官必问的问题


Lambda表达式说爱你不容易


分享面试:mysql数据库索引失效的情况


Spring-Boot中一个不起眼的好工具StopWatch