什么是哈希冲突?
哈希冲突(Hash Collision)是指在使用哈希表存储数据时,两个或多个不同的键(Key)被哈希函数映射到同一个位置的情况。这种情况会导致数据的存储和查找变得复杂,因此需要采取一些措施来解决哈希冲突。
避免Hash冲突的主要方法是设计一个好的哈希函数和处理冲突的策略。
以下是一些常见的方法:
● 设计一个好的哈希函数:一个好的哈希函数应该满足以下两个条件:一是均匀性,即每个输入值被映射到输出值的概率应该是相等的;二是确定性,即对于同一个输入值,无论何时运行哈希函数,都应该得到相同的输出值。
● 链地址法:当发生冲突时,将所有哈希值相同的元素放在一个链表中。查找元素时,先计算元素的哈希值,然后在对应的链表中进行查找。
● 开放地址法:当发生冲突时,寻找下一个可用的空槽,直到找到一个空槽为止。这种方法的缺点是需要在表中预留一些空槽,以备后续插入使用。
● 二次哈希:当发生冲突时,使用第二个哈希函数来计算新的位置。这种方法的缺点是需要设计第二个哈希函数。
● 动态调整哈希表的大小:当哈希表的负载因子(已存储的元素数量与哈希表大小的比值)超过一定阈值时,将哈希表的大小扩大一倍,并重新哈希所有的元素。
以上方法可以有效地减少Hash冲突的发生,但无法完全避免。
Java是如何解决Hash冲突的?
在Java中,HashMap 和 HashSet 等基于哈希的集合类使用链地址法(Separate Chaining)来解决哈希冲突。这意味着每个哈希桶(bucket)对应一个链表,当不同的键映射到同一个哈希桶时,这些键会被存储在同一个链表中。在Java中,当发生冲突时,新的键值对会被添加到链表的末尾。
Java中的HashMap在解决哈希冲突时,会随着冲突的增多而将链表转换为平衡树(红黑树),这种转换发生在链表长度超过某个阈值(默认为8)时,以提高搜索效率。这是因为当链表变得很长时,遍历链表的时间复杂度为O(n),而平衡树的搜索时间复杂度为O(log n),因此在包含大量元素且发生大量冲突的情况下,平衡树可以提供更快的操作速度。
下面是Java中解决哈希冲突的基本步骤:
当一个键值对被插入到HashMap中时,首先会使用哈希函数计算键的哈希码。
哈希码会映射到一个数组索引,这个索引对应于哈希桶的位置。
如果该位置为空,则直接将键值对存储在该位置。
如果该位置已经被占用(发生了哈希冲突),则会查看该位置上的链表。
遍历链表,如果找到了具有相同键的节点,则更新该节点的值。
如果没有找到具有相同键的节点,则将新的键值对添加到链表的末尾。
如果链表长度超过阈值(默认为8),并且哈希桶数组的长度超过64,则会将链表转换为平衡树,以提高搜索效率。
当HashMap中的元素数量过多时,为了保持性能,HashMap会进行扩容操作,即创建一个新的更大的哈希桶数组,并将所有现有的键值对重新哈希到新数组中。扩容操作通常发生在元素数量达到容量和负载因子(默认为0.75)的乘积时。扩容是一个相对昂贵的操作,因为它涉及到重新计算所有键的哈希码并将它们重新插入到新的哈希桶中。
测试一下大概出现Hash冲突的概率:
/**
* @author wheelmouse
* @date 2024-03-25 19:07
*/
public class HashConflict {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
int hashcode = 0;
int count = 11 * 10000;
for (int i = 0; i < count; i++) {
hashcode = new Object().hashCode();
if (!set.contains(hashcode)) {
set.add(hashcode);
} else {
System.out.println("第" + i + "个对象发生重复了!");
}
}
}
}


本文探讨了哈希冲突的概念,介绍了避免冲突的几种策略,如好的哈希函数设计、链地址法、开放地址法和动态调整哈希表。重点讲述了Java中HashMap处理哈希冲突的机制,包括使用链地址法、转换为平衡树以及扩容操作。同时,通过代码示例测试了哈希冲突的大致概率。
3445

被折叠的 条评论
为什么被折叠?



