面试复盘:哈希冲突的常见解决方案?

惰性星轨
• 阅读 353

哈希冲突是指在哈希表中,两个或多个元素被映射到了同一个位置的情况。

String str1 = "3C";
String str2 = "2b";
int hashCode1 = str1.hashCode();
int hashCode2 = str2.hashCode();
System.out.println("字符串: " + str1 + ", hashCode: " + hashCode1);
System.out.println("字符串: " + str2 + ", hashCode: " + hashCode2);

程序的运行结果如下:
面试复盘:哈希冲突的常见解决方案?
不同的字符串,却拥有了相同的 hashCode 这就是哈希冲突。因为元素的位置是根据 hashCode 的值进行定位的,此时它们的 hashCode 相同,但一个位置只能存储一个值,这就是哈希冲突。

解决哈希冲突

在 Java 中,解决哈希冲突的常用方法有以下三种:链地址法、开放地址法和再哈希法。

  1. 链地址法(Separate Chaining):将哈希表中的每个桶都设置为一个链表,当发生哈希冲突时,将新的元素插入到链表的末尾。这种方法的优点是简单易懂,适用于元素数量较少的情况。缺点是当链表过长时,查询效率会降低。
  2. 开放地址法(Open Addressing):当发生哈希冲突时,通过一定的探测方法(如线性探测、二次探测、双重哈希等)在哈希表中寻找下一个可用的位置。这种方法的优点是不需要额外的存储空间,适用于元素数量较多的情况。缺点是容易产生聚集现象,即某些桶中的元素过多,而其他桶中的元素很少。
  3. 再哈希法(Rehashing):当发生哈希冲突时,使用另一个哈希函数计算出一个新的哈希值,然后将元素插入到对应的桶中。这种方法的优点是简单易懂,适用于元素数量较少的情况。缺点是需要额外的哈希函数,且当哈希函数不够随机时,容易产生聚集现象。

链地址法 VS 开放地址法

链地址法和开放地址法个人觉得以下几点不同:

  1. 存储结构不同:链地址法规定了存储的结构为链表(每个桶为一个链表),每次将值存储到链表的末尾;而开放地址法未规定存储的结构,所以它可以是链表也可以是树结构等。
  2. 查找方式不同:链地址法查找时,先通过哈希函数计算出哈希值,然后在哈希表中查找对应的链表,再遍历链表查找对应的值。而开放地址法查找时,先通过哈希函数计算出哈希值,然后在哈希表中查找对应的值,如果查找到的值不是要查找的值,就继续查找下一个值,直到查找到为止。
  3. 插入方法不同:链地址法插入时,先通过哈希函数计算出哈希值,然后在哈希表中查找对应的链表,再将值插入到链表的末尾。而开放地址法插入时,是通过一定的探测方法,如线性探测、二次探测、双重哈希等,在哈希表中寻找下一个可用的位置。所以链地址法插入方法实现非常简单,而开放地址法插入方法实现相对复杂。

线性探测 VS 二次探测

线性探测是发生哈希冲突时,线性探测会在哈希表中寻找下一个可用的位置,具体来说,它会检查哈希表中下一个位置是否为空,如果为空,则将元素插入该位置;如果不为空,则继续检查下一个位置,直到找到一个空闲的位置为止。

二次探测是发生哈希冲突时,二次探测会使用一个二次探测序列来寻找下一个可用的位置,具体来说,它会计算出一个二次探测序列,然后依次检查哈希表中的每个位置,直到找到一个空闲的位置为止。二次探测的优点是相对于线性探测来说,它更加均匀地分布元素,缺点是当哈希表的大小改变时,需要重新计算二次探测序列。

具体来说,二次探测序列是一个二次函数,它的形式如下:

f(i) = i^2

其中,i 表示探测的步数,f(i) 表示探测的位置。

例如,当发生哈希冲突时,如果哈希表中的第 k 个位置已经被占用,那么二次探测会依次检查第 k+1^2、第 k-1^2、第 k+2^2、第 k-2^2、第 k+3^2、第 k-3^2……等位置,直到找到一个空闲的位置为止。

二次探测的优点是相对于线性探测来说,它更加均匀地分布元素,但缺点是容易产生二次探测聚集现象,即某些桶中的元素过多,而其他桶中的元素很少。

HashMap 如何解决哈希冲突?

在 Java 中,HashMap 使用的是开放地址法解决哈希冲突的,因为在 JDK 1.8 之后(包含 JDK 1.8),HashMap 使用的数组 + 链表或红黑树的结构来存储数据了,所以显然不能使用链地址法来解决哈希冲突。
面试复盘:哈希冲突的常见解决方案?

本文已收录至《Java面试突击》,专注 Java 面试 100 年,查看更多:www.javacn.site
点赞
收藏
评论区
推荐文章
东方客主 东方客主
4年前
go-map源码简单分析(map遍历为什么时随机的)
GO中map的底层是如何实现的首先Go语言采用的是哈希查找表,并且使用链表解决哈希冲突。GO的内存模型先看这一张map原理图(https://imghelloworld.osscnbeijing.aliyuncs.com/49dfa7b81e19fbab143ddc0a7b3b7fa0.png)map再来看
Wesley13 Wesley13
3年前
java容器之HashMap
HashMap采用了数组和链表的数据结构,能在查询和修改方便继承了数组的线性查找和链表的寻址修改,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。解决哈希冲突的三个方法:a.开放定址法  又被称为再散列法,包括线性探测再散列、二次探测再散列、伪随机探测再散列b.再哈希法  地址冲突后,对哈希结果再次进行哈希,直到
BichonCode BichonCode
4年前
大数据排序
一、如何给100亿个数字进行排序? 1.1解答:1.把这个37GB的大文件,用哈希分成1000个小文件,每个小文件平均38MB左右(理想情况),把100亿个数字对1000取模,模出来的结果在0到999之间,每个结果对应一个文件,所以我这里取的哈希函数是hx%1000,哈希函数取得"好",能使冲突减小,结果分布均匀。2.拆分完了之后
哈希游戏搭建开发主要原理
区块链的算法主要有两个部分,一个是哈希算法,一个是非对称加密。哈希(Hash)是一种加密算法,也称为散列函数或杂凑函数。哈希函数是一个公开函数,可以将任意长度的消息M映射成为一个长度较短且长度固定的值H(M),称H(M)为哈希值、散列值(HashValue)、杂凑值或者消息摘要。它是一种单向密码体制,即一个从明文到密文的不可逆映射,只有加密过程,没有解密过
Stella981 Stella981
3年前
Consistent hashing一致性算法原理
最近在整理redis分布式集群,首先就整理一下分布式算法原理。常见的分区规则有哈希分区和顺序分区两种,Redis采用的是哈希分区规则。节点取余分区使用特定的数据,如Redis的键或用户ID为key,节点数量为N,则:hash(key)%N,计算出哈希值,然后决定映射到哪个节点上,如节点数为4时,哈希值的结果可能为0、1、2,3.现假
Wesley13 Wesley13
3年前
Java集合之Map接口
Map使用键值对来存储数据,将键映射到值对象,一个映射不能包含重复的键,每一个键最多只能映射到一个值。Map接口的具体实现类:HashMap,Hashtable,TreeMap,LinkedHashMap  1)HashMap  基于哈希表(哈希表学习地址)的Map接口实现。允许使用null值和null键,不保证映射的顺序,特别是不保证顺序恒
Wesley13 Wesley13
3年前
MySQL索引初探
一、什么是索引?帮助数据库系统实现高效获取数据的数据结构索引可以帮助我们快速地定位到数据而不需要每次搜索的时候都遍历数据库中的每一行。二、常见实现方式有哪些?常见索引模型有三种:哈希表、有序数组、搜索树1.哈希表(1)使用哈希表实现的索引称为哈希索引。!(https://os
贾蔷 贾蔷
2个月前
手把手教你实现哈希表:从代码到原理的新手友好指南
一、简介和应用哈希表(HashTable)是一种高效的数据结构,通过哈希函数将键(Key)映射到存储位置,实现O(1)时间复杂度的查找、插入和删除操作。它广泛应用于缓存系统、数据库索引、字典查询等场景。例如,在编程中需要快速根据用户ID查找信息时,哈希表能
V-275670029 V-275670029
3年前
哈希竞猜游戏是什么
区块链中的哈希到底是什么?  什么是哈希?  哈希是将任意长的输入编程加密的固定长度输出的过程。哈希并不等同于加密方法,因为无法解密哈希值来获取原始数据。事实上哈希是一种单向加密函数。  Withthehashfunction,thedataontheInternetcanbesavedintheformofafixedle
V-275670029 V-275670029
3年前
哈希竞猜游戏开发搭建包网
区块链中的哈希到底是什么?  什么是哈希?  哈希是将任意长的输入编程加密的固定长度输出的过程。哈希并不等同于加密方法,因为无法解密哈希值来获取原始数据。事实上哈希是一种单向加密函数。  Withthehashfunction,thedataontheInternetcanbesavedintheformofafixedle
贾蔷 贾蔷
2个月前
哈希表实现指南:从原理到C++实践
一、简介和应用哈希表(HashTable)是一种高效的数据结构,通过键值对(keyvalue)存储数据,提供快速的插入、删除和查找操作。它使用哈希函数将键映射到表中的位置,使得平均时间复杂度可以达到O(1)。‌应用场景‌:数据库索引、缓存实现(如Redis