HashMap详解-在JDK1.7与1.8中HashMap的区别

引言

HashMap源码在JDK不同的版本中的模样不尽相同,这篇文章主要了解JDK1.7与1.8中的HashMap有什么区别

在了解该文内容之前,可以先浏览一下我之前的文章,可能会对你有一些帮助
HashMap详解-内部实现原理(1)-数组和桶
HashMap详解-内部实现原理(2)-哈希函数
HashMap详解-内部实现原理(3)-扩容机制
HashMap详解-HashMap 的常见操作

数据结构的改变

HashMap的存储组合的变化

在 JDK 1.7 中,HashMap 使用了数组和链表的组合来存储数据。每个数组元素存储一个链表,如果多个键的哈希值相同,则它们会被放置在同一个链表中。

而在 JDK 1.8 中,HashMap 引入了红黑树来解决哈希冲突。当链表的长度超过阈值(默认为 8)时,链表会转化为红黑树,以提高查询和删除的效率,即JDK1.8中HashMap使用数组+链表/红黑树进行数据的存储。

结构变化的示例

在 JDK 1.8 中,HashMap 的存储结构改进主要涉及引入了新的抽象类 Node,用于表示存储在桶中的节点。这个类有两个子类,分别是 TreeNode 和 Entry。Entry 表示普通的链表节点,而 TreeNode 则表示红黑树节点。

通过引入 Node 类,JDK 1.8 可以更好地支持链表和红黑树两种数据结构,并根据具体情况进行选择,以提高查询和删除的效率。

下面是一个示例来说明存储结构的改进:

假设我们向一个空的 HashMap 中插入四个键值对 (key1, value1)、(key2, value2)、(key3, value3)、(key4, value4),它们的哈希值都为相同的值。

在 JDK 1.7 中,存储结构如下所示:

bucket 0: [key1 -> value1] → [key2 -> value2] → [key3 -> value3] → [key4 -> value4]

每个桶中存储了一个链表,多个键的哈希值相同时,它们会被放置在同一个链表中。

而在 JDK 1.8 中,存储结构则变为:

bucket 0: [TreeNode(key1, value1)]

由于插入的键值对数量达到了阈值(默认为 8),链表会转化为红黑树,存储结构如下所示:

bucket 0: [TreeNode(key1, value1)] → [TreeNode(key2, value2)] → [TreeNode(key3, value3)] → [TreeNode(key4, value4)]

通过存储结构的改进,JDK 1.8 在处理冲突时可以更加高效地使用链表和红黑树,并根据实际情况选择更适合的数据结构,以提高 HashMap 的性能和效率。

扩容机制的改变

JDK1.7与1.8不同的扩容过程

在 JDK 1.7 中,HashMap 在扩容时会通过重新计算哈希码和重新分配桶位置的方式来实现扩容。具体过程如下:

  1. 当 HashMap 中的键值对数量超过负载因子(默认为 0.75)与桶数组长度的乘积时,会触发扩容操作。
  2. 扩容时,会创建一个新的桶数组,其长度为原数组长度的两倍。
  3. 遍历原桶数组,将其中的键值对重新计算哈希码,并根据新的数组长度,重新分配到新的桶中。

然而,在 JDK 1.8 中,除了上述的哈希码重新计算和桶位置重新分配外,还引入了对红黑树的重建,以保持树的平衡性。具体过程如下:

  1. 当链表的长度超过阈值(默认为 8)时,链表会转化为红黑树,以提高查询和删除的效率。
  2. 扩容时,会创建一个新的桶数组,其长度为原数组长度的两倍。
  3. 遍历原桶数组,将其中的键值对重新计算哈希码,并根据新的数组长度,重新分配到新的桶中。
  4. 对于原来的链表节点,仍然保留链表结构。
  5. 对于原来的红黑树节点,会进行重建,以保持树的平衡性。
可能通过文字了解很抽象,下面是一个扩容机制改变的示例

假设我们有一个初始容量为 4 的 HashMap,并插入以下键值对:

(key1, value1)、(key2, value2)、(key3, value3)、(key4, value4)、(key5, value5)、(key6, value6)、(key7, value7)、(key8, value8)。

在 JDK 1.7 中,HashMap 的存储结构如下所示:

bucket 0: [key1 -> value1] → [key5 -> value5]
bucket 1: [key2 -> value2] → [key6 -> value6]
bucket 2: [key3 -> value3] → [key7 -> value7]
bucket 3: [key4 -> value4] → [key8 -> value8]

当 HashMap 中的键值对数量超过负载因子(默认为 0.75)与桶数组长度的乘积时,即 8 > 0.75 * 4 = 3,触发扩容操作。此时,会创建一个新的桶数组,长度为原数组长度的两倍,即 4 * 2 = 8。

扩容后,HashMap 的存储结构如下:

bucket 0: [key1 -> value1] → [key5 -> value5]
bucket 1: [key2 -> value2] → [key6 -> value6]
bucket 2:
bucket 3: [key3 -> value3] → [key7 -> value7]
bucket 4: [key4 -> value4] → [key8 -> value8]

可以看到,新的桶数组长度变为 8,并且重新计算哈希码并分配到新的桶中。链表结构得以保留。

而在 JDK 1.8 中,当链表的长度超过阈值(默认为 8)时,链表会转化为红黑树。假设我们再继续插入两个键值对:

(key9, value9)、(key10, value10)。

在 JDK 1.7 中,存储结构不发生变化。但在 JDK 1.8 中,由于链表长度超过了阈值,链表会转化为红黑树。此时,HashMap 的存储结构如下:

bucket 0: [key9 -> value9] → [key1 -> value1] → [key5 -> value5]
bucket 1: [key2 -> value2] → [key6 -> value6]
bucket 2: [key10 -> value10] → [key3 -> value3] → [key7 -> value7]
bucket 3: [key4 -> value4] → [key8 -> value8]

可以看到,链表已经转化为红黑树,以提高查询和删除的效率。

通过这个示例,我们可以清楚地看到 JDK 1.8 在扩容机制方面的改变。除了重新计算哈希码和桶位置外,还引入了对红黑树的重建。

这一改变使得在扩容时,HashMap 不仅重新计算哈希码和重新分配桶位置,还会对红黑树进行重建。通过重建红黑树,可以保持树的平衡性,避免出现过深的树结构,提高了查询和删除的效率。

总之,在 JDK 1.8 中,扩容机制相较于 JDK 1.7 进行了优化,不仅保留了链表结构,还对红黑树进行了重建,以提高 HashMap 的性能和效率。

键值对遍历的改进

在 JDK 1.8 中,HashMap 对键值对的遍历进行了改进。具体来说,主要引入了两个新方法:forEach() 和 replaceAll(),接下来分别解析新的方法。

  1. forEach 方法:
    这个方法接受一个 Lambda 表达式作为参数,可以对键值对进行迭代处理。Lambda 表达式需要传入一个 BiConsumer 参数,其中第一个参数表示键,第二个参数表示值。通过这个方法,可以方便地对 HashMap 中的每个键值对进行操作,比如打印、修改等。示例代码如下:
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("key1", 1);
hashMap.put("key2", 2);
hashMap.put("key3", 3);

hashMap.forEach((key, value) -> {
    System.out.println(key + " : " + value);
});

输出结果如下:

key1 : 1
key2 : 2
key3 : 3
  1. replaceAll 方法:
    这个方法接受一个 UnaryOperator 函数作为参数,用于对键值对的值进行替换。UnaryOperator 函数需要传入一个参数表示原始值,并返回一个新的值。通过这个方法,可以对 HashMap 中的每个值进行替换操作,比如加倍、减半等。示例代码如下:
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("key1", 1);
hashMap.put("key2", 2);
hashMap.put("key3", 3);

hashMap.replaceAll((key, value) -> value * 2);

hashMap.forEach((key, value) -> {
    System.out.println(key + " : " + value);
});

结果如下:

key1 : 2
key2 : 4
key3 : 6

总结

通过深入比较这两个版本的源码,我们可以更好地理解 HashMap 的演化过程和性能优化。这些改进使得 JDK 1.8 的 HashMap 在性能、并发性和可读性方面都有了显著提升,成为了一个更加强大和高效的数据结构。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值