解决哈希表冲突的方法

1. 拉链法(Chaining)
原理:拉链法将所有哈希值相同的元素放在一个链表或其他集合数据结构中。每个桶的内部存储不是单个值,而是一个链表,哈希冲突的元素会加入到该链表中。
实现:Java 的 HashMap 就是使用拉链法来解决哈希冲突。在 HashMap 中,每个桶实际上是一个链表(在 Java 8 之后,如果链表长度超过某个阈值会转换为红黑树以提高查询效率)。
优点:

简单直观,容易实现。
插入和删除操作相对简单。
缺点:

在最坏情况下(所有键值都冲突),时间复杂度退化为 O(n)。
链表可能占用较多内存。
2. 开放地址法(Open Addressing)
原理:在出现哈希冲突时,通过寻找表中其他空闲的位置来存储元素。具体有几种策略:
线性探测(Linear Probing:当发生冲突时,依次检查下一个位置,直到找到一个空闲的桶。
二次探测(Quadratic Probing):与线性探测类似,但检查的位置是通过二次方增加的(例如检查位置 i + 1^2, i + 2^2, …)。
双重哈希(Double Hashing):使用另一个哈希函数来计算冲突后应该检查的位置。
优点:

节省空间,所有元素都直接存储在哈希表中。
对于适量的冲突,访问速度会较快。
缺点:

当表变得较满时,探测的次数会增加,效率降低。
删除操作较为复杂,因为需要处理探测路径中断的问题。
3. 再哈希法(Rehashing)
原理:当发生哈希冲突时,使用不同的哈希函数计算新的哈希值,直到找到一个不冲突的位置。双重哈希就是再哈希的一种形式。
优点:

减少了连续探测的次数,冲突概率低。
缺点:

需要多个哈希函数,计算复杂度增加。
4. 扩展哈希表(Resizing and Rehashing)
原理:当哈希表中的元素过多,导致冲突频繁时,可以通过扩展哈希表的容量来减少冲突。Java 中的 HashMap 会在元素数量达到一定比例时(默认是容量的 75%)进行扩容,并重新分配所有元素(即重新计算哈希值和桶位置)。
优点:
减少哈希冲突,提高查询效率。
缺点:

扩容和再哈希的成本较高,尤其是在大规模数据的情况下。
Java 中 HashMap 的解决哈希冲突方式
在 Java 的 HashMap 中,主要使用的是拉链法,并在链表长度超过阈值时将链表转换为红黑树。具体来说:

链表(Java 8 之前):冲突的元素被添加到链表中。查找时间复杂度在最坏情况下是 O(n)。
红黑树(Java 8 之后):当链表长度超过 8 时,链表会被转换为红黑树,以提高查找效率。红黑树的时间复杂度为 O(log n)。
总结
解决哈希冲突的方式有多种,但 Java 中 HashMap 主要使用的是拉链法,并结合了链表和红黑树,以平衡性能和空间消耗。在极端情况下,还会通过扩展哈希表来减少冲突。

hashmap扩容 原理

JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。 JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于等于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。并且, HashMap 总是使用 2 的幂作为哈希表的大小。

执行顺序
首先判断是否需要将链表转换为红黑树:如果链表长度超过 8 且哈希表容量大于 64,则进行转换。
如果容量较小(小于 64)且链表过长,则不会转换为红黑树,而是选择扩容。
扩容:如果哈希表的负载因子超过 0.75,则扩容,并将现有元素重新分布到新的哈希表中。
总结
链表转换为红黑树的优先级高:当链表长度超过阈值(8),且哈希表容量大于等于 64 时,先进行转换。
扩容的优先级较低:如果链表较长但容量较小(小于 64),则会先扩容再考虑红黑树转换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值