全文目录:
开篇语
今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!
前言
在 Java 编程中,HashMap
是最常用的集合类之一,它提供了基于键值对的高效存储和查找功能。你是否曾经好奇过,当我们调用 put()
方法往 HashMap
中插入元素时,究竟发生了什么?今天,我就带你深度解析 HashMap
的 put()
方法执行过程,揭开背后的神秘面纱!
1. put()
方法的基本概述
put()
方法是 HashMap
中用来插入或更新元素的核心方法。它的签名如下:
V put(K key, V value);
- key:存储的键
- value:存储的值
- 返回值:
put()
方法会返回键对应的旧值。如果该键之前没有对应的值,则返回null
。
如果键已经存在,则会更新该键对应的值;如果键不存在,则会插入一个新的键值对。
2. put()
方法的执行过程
在 HashMap
中,put()
方法的执行过程可以分为几个关键步骤,我们通过下图来简要总结每个步骤的内容:
步骤 1:计算哈希值
put()
方法首先会计算插入的键的哈希值。HashMap
是基于哈希表实现的,因此哈希值决定了键值对的存储位置。哈希值的计算使用了键的 hashCode()
方法,它返回一个整数作为哈希值。
但是,hashCode()
可能会存在哈希冲突的情况(即不同的键可能具有相同的哈希值)。为了尽量避免冲突,HashMap
会对哈希值做进一步的处理(通过扰动函数)来减少冲突的概率。
int hash = hash(key.hashCode());
步骤 2:定位桶(Bucket)
哈希值计算出来后,HashMap
会通过以下公式将其映射到具体的桶(即数组的索引)上:
int index = (n - 1) & hash; // n 是当前数组的长度,通常是 2 的幂
通过这个公式,HashMap
确定了哈希表中存储键值对的具体位置。此时,如果该位置为空(即没有其他元素占用该桶),put()
方法会直接将新的键值对插入到该位置。
步骤 3:判断是否有哈希冲突
如果该桶已经有元素了(哈希冲突的情况),那么 put()
方法会开始检查冲突元素的链表或红黑树结构。
- 链表法:如果哈希冲突较轻,
HashMap
会使用链表来存储多个键值对,所有哈希值相同的元素会在同一个桶中以链表的形式存储。 - 红黑树法:如果同一个桶中的元素数量超过一定阈值(通常是 8),
HashMap
会将链表转换成红黑树,以提高查找效率。
在链表或红黑树中,put()
方法会遍历现有的元素,判断当前要插入的键是否与链表或红黑树中的元素相同。
步骤 4:更新或插入元素
- 如果遍历到一个与新键相同的元素(即存在相同的键),那么
put()
方法会更新该键对应的值,并返回旧值。 - 如果没有找到相同的键,
put()
方法就会在该桶的链表或红黑树的末尾插入新的键值对。
步骤 5:扩容检查
每当元素数量达到负载因子(默认是 0.75)与当前容量的乘积时,HashMap
会触发扩容操作。扩容会将当前的容量扩展到原来的两倍,并重新计算所有元素的存储位置。扩容时,所有现有的元素都会重新计算哈希值并放入新的桶中,这一过程会导致性能下降,所以 put()
方法会尽量避免频繁的扩容操作。
扩容的触发条件如下:
if (size > threshold) {
resize();
}
其中,threshold
是负载因子与当前容量的乘积,size
是当前元素的数量。
3. put()
方法的源码解析
为了更深入理解 put()
方法的实现,以下是 HashMap
中 put()
方法的简化版源码分析:
public V put(K key, V value) {
// 1. 计算哈希值
int hash = hash(key.hashCode());
// 2. 获取桶的索引
int index = (n - 1) & hash;
// 3. 获取对应桶
Node<K, V> first = table[index];
// 4. 如果该桶为空,直接插入
if (first == null) {
table[index] = newNode(hash, key, value, null);
size++;
if (size > threshold)
resize();
return null;
}
// 5. 如果该桶非空,查找是否存在相同键
Node<K, V> node = first;
do {
if (node.hash == hash && (key == node.key || key.equals(node.key))) {
// 如果找到相同键,更新值
V oldValue = node.value;
node.value = value;
return oldValue;
}
node = node.next;
} while (node != null);
// 6. 没有找到相同键,插入新元素
table[index] = newNode(hash, key, value, first);
size++;
if (size > threshold)
resize();
return null;
}
在这段代码中:
hash()
:计算并扰动哈希值。index
:根据哈希值和桶的大小计算出元素应存放的位置。- 如果桶为空,直接创建新的节点。
- 如果桶非空,遍历链表(或红黑树)检查是否有相同键的元素,若有则更新值,若没有则插入新元素。
- 在每次插入或更新后,都会检查是否需要扩容。
4. put()
方法的性能分析
put()
方法的性能受到多个因素的影响:
- 哈希冲突:当多个键映射到同一个桶时,查找、插入的时间复杂度会从 O(1) 增加到 O(n),其中 n 是桶中元素的数量。为了减少哈希冲突,
HashMap
会通过扰动函数和扩容机制来尽量保持均匀分布。 - 扩容:扩容是一个代价较大的操作,因为需要重新计算所有元素的哈希值,并将它们重新分配到新的桶中。因此,频繁的扩容会影响性能。合理的负载因子和容量设置能有效减少扩容的次数。
总结
通过上面的分析,我们可以看到,HashMap
的 put()
方法是一个复杂的过程,涉及到哈希值计算、桶定位、哈希冲突处理、元素插入和扩容等多个步骤。尽管如此,得益于其合理的设计,HashMap
在平均情况下能够提供 O(1) 的时间复杂度。然而,哈希冲突、扩容等因素仍然可能导致性能下降,因此合理选择容量和负载因子至关重要。
理解了 put()
方法的实现过程,你就能更好地掌握 HashMap
的性能优化技巧,并在实际开发中做出更好的选择!
… …
文末
好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。
… …
学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!
wished for you successed !!!
⭐️若喜欢我,就请关注我叭。
⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。
版权声明:本文由作者原创,转载请注明出处,谢谢支持!