Java进阶—哈希冲突的解决
1. 什么是哈希冲突
哈希函数:哈希函数是一种将输入数据(键)映射到固定大小范围的输出值(哈希值)的函数。哈希函数通常用于存储 数据存储和检索领域,例如哈希表中。
哈希表:哈希表(Hash Table),也成为哈希映射(Hash Map)或字典(Dictionary),是一种常见的数据结构,用于实现关联数组,它可以将键映(Key)射到值(Value)
哈希冲突:由于哈希算法被计算的数据是无限的,而计算后的结果范围有限,当两个或更多键被哈希函数映射到相同的索引位置时,就会发生哈希冲突。
在Java中,哈希函数通常是通过hashCode()方法实现的,hashCode()方法是Object类中定义的一个方法,因此所有Java类都可以使用它。hashCode()方法的默认实现是返回对象的内存地址的哈希码,但通常情况下,我们需要重写这个方法来提供更合适的哈希函数。
在重写hashCode()方法时,通常需要遵循以下法则:
- 相等的对象必须具有相等哈希码。也就是说,如果两个对象通过‘equals()’方法相等,那么它们的哈希码应该相等。
如果 a.equals(b)==true,它们的hashCode一定相同吗?
哈希表中,首先会比较对象的‘hashCode’值确定它们所在的桶(bucket),然后再根据‘equals()’方法来精确匹配对象。
所以在Java中a.equals(b)==true,它们的hashCode一定是相同的。除非重写了equals方法或hashCode方法()
因此,为了保持一致性,在重写equals()方法时通常也需要重写hashCode()方法,以确保相等的对象具有相同的哈希码。
-
尽量减少哈希冲突,即不同的对象尽量产生不同的哈希码,以提高哈希表等数据结构的性能。
例如,对于一个简单的Person类,我们可以重写hashCode()方法如下:public class Person {private String name;private int age;// 构造函数、getter和setter等方法省略@Overridepublic int hashCode() {int result = 17;result = 31 * result + name.hashCode();result = 31 * result + age;return result;} }在这个例子中,我们使用了一种常见的计算哈希码的方式,即将一个基本数值(这里是17)与对象的属性哈希码相结合,然后用一个质数(31)作为乘数不断累加。这种方式可以比较有效地减少哈希冲突,但并不是万能的,具体的哈希函数设计取决于对象的属性特点和应用场景。
2. 怎么解决哈希冲突
为了解决哈希冲突,常见的方法包括开放寻址法和链表法(拉链法)。
2.1 开放寻址法
在发生哈希冲突时,通过一定的方法(如线性探测、二次探测、双重哈希等)寻找下一个空的哈希表位置,键冲突的键值对放置在新的位置上。这样可以避免使用额外的数据结构存储冲突的键值对,节省空间。但是,如果哈希表填充率过高,开放寻址法的性能可能会收到影响。
-
线性探测(Linear Probing):在发生哈希冲突时,线性探测会顺序地检查哈希表中的下一个位置,直到找到一个空闲的位置为止。具体地说,如果哈希表中位置‘i’发生了冲突,那么线性探索位置‘i+1’,‘i+2’,直到找到一个空闲位置或遍历完整个哈希表。正常不会发生找不到位置,通常情况下,但哈希表中元素达到一定阈值,会自动触发扩容操作。
当哈希表使用线性探测解决哈希冲突时,插入和查找元素的过程如下:
-
插入元素时,如果计算出的哈希位置已经被占用,则向后顺序查找直到找到一个空闲位置。
-
查找元素时,计算出元素的哈希位置后,如果该位置为空或者元素的键与要查找的键不相等,则向后顺序查找直到要查找的元素或者遍历完整个哈希表
以下是使用线性探测法解决哈希冲突的简单示例代码:class LinearProbingHashTable {private static final int DEFAULT_CAPACITY = 10;private Entry[] table;private int size;public LinearProbingHashTable() {table = new Entry[DEFAULT_CAPACITY];size = 0;}public void put(int key, String value) {int index = hash(key);while (table[index] != null && table[index].getKey() != key) {index = (index + 1) % table.length; // 线性探测下一个位置}if (table[index] == null) {table[index] = new Entry(key, value);size++;} else {table[index].setValue(value);}}public String get(int key) {int index = hash(key);while (table[index] != null && table[index].getKey() != key) {index = (index + 1) % table.length; // 线性探测下一个位置}return table[index] != null ? table[index].getValue() : null;}private int hash(int key) {return key % table.length;}private static class Entry {private int key;private String value;public Entry(int key, String value) {this.key = key;this.value = value;}public int getKey() {return key;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}} }public class Main {public static void main(String[] args) {LinearProbingHashTable hashTable = new LinearProbingHashTable();hashTable.put(1, "A");hashTable.put(11, "B");hashTable.put(21, "C");System.out.println(hashTable.get(1)); // 输出 "A"System.out.println(hashTable.get(11)); // 输出 "B"System.out.println(hashTable.get(21)); // 输出 "C"} }
-
-
二次探测(Quadratic Probing):二次探测是线性探测地改进版本,它使用一个二次方程来计算下一个探测位置。具体来说,如果哈希表中位置‘i’发生了冲突,那么二次探测法会一次检查位置 (i + 1^2)、(i + 2^2)、(i + 3^2),直到找到一个空闲位置或者遍历完整个哈希表。
以下是使用二次探测解决哈希冲突的示例代码:
class QuadraticProbingHashTable {private static final int DEFAULT_CAPACITY = 10;private Entry[] table;private int size;public QuadraticProbingHashTable() {table = new Entry[DEFAULT_CAPACITY];size = 0;}public void put(int key, String value) {int index = hash(key);int i = 1;while (table[index] != null && table[index].getKey() != key) {index = (index + i * i) % table.length; // 二次探测下一个位置i++;}if (table[index] == null) {table[index] = new Entry(key, value);size++;} else {table[index].setValue(value);}}public String get(int key) {int index = hash(key);int i = 1;while (table[index] != null && table[index].getKey() != key) {index = (index + i * i) % table.length; // 二次探测下一个位置i++;}return table[index] != null ? table[index].getValue() : null;}private int hash(int key) {return key % table.length;}private static class Entry {private int key;private String value;public Entry(int key, String value) {this.key = key;this.value = value;}public int getKey() {return key;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}} }public class Main {public static void main(String[] args) {QuadraticProbingHashTable hashTable = new QuadraticProbingHashTable();hashTable.put(1, "A");hashTable.put(11, "B");hashTable.put(21, "C");System.out.println(hashTable.get(1)); // 输出 "A"System.out.println(hashTable.get(11)); // 输出 "B"System.out.println(hashTable.get(21)); // 输出 "C"} } -
双重哈希(Double Hashing):双重哈希使用两个哈希函数来计算下一个探测位置。具体地说,如果哈希表中位置‘i’发生了冲突,双重哈希会使用第二个哈希函数计算一个偏移量来计算下一个探测位置。如果这个位置仍然发生冲突,就会使用相同的方法,直到找到一个空间位置或者遍历完整个哈希表。
以下是使用双重哈希解决哈希冲突的示例代码:class DoubleHashingHashTable {private static final int DEFAULT_CAPACITY = 10;private Entry[] table;private int size;public DoubleHashingHashTable() {table = new Entry[DEFAULT_CAPACITY];size = 0;}public void put(int key, String value) {int index = hash1(key);int offset = hash2(key);while (table[index] != null && table[index].getKey() != key) {index = (index + offset) % table.length; // 双重哈希探测下一个位置}if (table[index] == null) {table[index] = new Entry(key, value);size++;} else {table[index].setValue(value);}}public String get(int key) {int index = hash1(key);int offset = hash2(key);while (table[index] != null && table[index].getKey() != key) {index = (index + offset) % table.length; // 双重哈希探测下一个位置}return table[index] != null ? table[index].getValue() : null;}private int hash1(int key) {return key % table.length;}private int hash2(int key) {return 7 - (key % 7); // 可以选择不同的哈希函数}private static class Entry {private int key;private String value;public Entry(int key, String value) {this.key = key;this.value = value;}public int getKey() {return key;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}} }public class Main {public static void main(String[] args) {DoubleHashingHashTable hashTable = new DoubleHashingHashTable();hashTable.put(1, "A");hashTable.put(11, "B");hashTable.put(21, "C");System.out.println(hashTable.get(1)); // 输出 "A"System.out.println(hashTable.get(11)); // 输出 "B"System.out.println(hashTable.get(21)); // 输出 "C"} }
2. 2 链表法(拉链法)
链表法(Separte Chaining)是一种常见到的解决哈希冲突的方法,它使用链表来存储哈希表中发生冲突的键值对。具体来说,每个哈希表的槽位上都对应一个链表,当发生哈希冲突时,新的键值对会被插入到对应槽位上的链表中,而不是直接覆盖原有的键值对。
链表法适用于经常进行插入和删除的情况。
如下一组数字,(32、40、36、53、16、46、71、27、42、24、49、64)哈希表长度为13,哈希函数为H(key)=key%13,则链表法结果如下:
0
1 -> 40 -> 27 -> 53
2
3 -> 16 -> 42
4
5
6 -> 32 -> 71
7 -> 46
8
9
10 -> 36 -> 49
11 -> 24
12 -> 64
在java中,链接地址法也是HashMap解决哈希冲突的方法之一,jdk1.7完全采用单链表来存储同义词,jdk1.8则采用了一种混合模式,对于链表长度大于8的,会转换为红黑树存储。
以下是使用链表法解决哈希冲突的示例代码:
import java.util.*;class SeparateChainingHashTable {private static final int DEFAULT_CAPACITY = 10;private List<Entry>[] table;private int size;public SeparateChainingHashTable() {table = new LinkedList[DEFAULT_CAPACITY];size = 0;}public void put(int key, String value) {int index = hash(key);if (table[index] == null) {table[index] = new LinkedList<>();}for (Entry entry : table[index]) {if (entry.getKey() == key) {entry.setValue(value);return;}}table[index].add(new Entry(key, value));size++;}public String get(int key) {int index = hash(key);if (table[index] != null) {for (Entry entry : table[index]) {if (entry.getKey() == key) {return entry.getValue();}}}return null;}private int hash(int key) {return key % table.length;}private static class Entry {private int key;private String value;public Entry(int key, String value) {this.key = key;this.value = value;}public int getKey() {return key;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}}
}public class Main {public static void main(String[] args) {SeparateChainingHashTable hashTable = new SeparateChainingHashTable();hashTable.put(1, "A");hashTable.put(11, "B");hashTable.put(21, "C");System.out.println(hashTable.get(1)); // 输出 "A"System.out.println(hashTable.get(11)); // 输出 "B"System.out.println(hashTable.get(21)); // 输出 "C"}
}
3 建议和注意事项
- 选择合适的哈希函数: 哈希函数的选择对于减少哈希冲突非常重要。一个好的哈希函数应该能够将键均匀地分布到哈希表中的槽位上,减少碰撞的概率。
- 考虑装载因子: 装载因子是指哈希表中已存储键值对的数量与哈希表总容量的比值。当装载因子过高时,哈希冲突的概率会增加,影响性能。因此,定期调整哈希表的大小,以保持适当的装载因子是很重要的。
- 选择合适的解决冲突方法: 根据应用场景和性能需求,选择合适的解决哈希冲突的方法。例如,开放寻址法适用于空间紧张的情况,而链表法适用于处理大量哈希冲突的情况和频繁插入删除的操作。
- 考虑并发情况: 在并发环境下,需要考虑多线程同时访问哈希表可能引发的问题。可以使用线程安全的哈希表实现或者在访问哈希表时进行适当的同步操作来处理并发访问问题。
相关文章:
Java进阶—哈希冲突的解决
1. 什么是哈希冲突 哈希函数:哈希函数是一种将输入数据(键)映射到固定大小范围的输出值(哈希值)的函数。哈希函数通常用于存储 数据存储和检索领域,例如哈希表中。 哈希表:哈希表(Hash Table),也成为哈希映射(Hash Map)或字典&…...
css的border详解
CSS的border属性是一个简写属性,用于设置以下四个边框属性: border-width:定义边框的宽度。可以使用具体的像素值,或者使用预定义的关键字如thin、medium和thick。border-width不支持百分比值。默认情况下,边框的宽度是…...
如何保障消息一定能发送到RabbitMQ?
我们知道,RabbitMQ的消息最终是存储在Queue上的,而在Queue之前还要经过Exchange,那么这个过程中就有两个地方可能导致消息丢失。第一个是Producer到Exchange的过程,第二个是Exchange到Queue的过程。 为了解决这个问题,…...
【web前端】CSS语法
CSS语法 1. CSS语法格式 通常情况下语法格式如下: 选择器{属性名:属性值;属性名:属性值;属性名:属性值;... }2. CSS添加方式 2.1 行内样式 直接将样式写在本行的标签内。 <h1><p style"font-size: 48px; color:red;";>行内样式测试</p></…...
JS+CSS3点击粒子烟花动画js特效
JSCSS3点击粒子烟花动画js特效 JSCSS3点击粒子烟花动画js特效...
docker镜像复制与常见命令
一、前言 最近通过阿里的镜像仓库远程拉取镜像,发现以前的版本不见了,拉取了最新的镜像,有发现版本不配问题。那么想使用老版本的镜像那就要从别的环境获取。于是就需要进行离线镜像复制,打包,上传,重新导入…...
如何在linux环境上部署单机ES(以8.12.2版本为例)
ES安装(以8.12.2版本为例) 首先创建好对应的文件夹然后在对应的文件夹下执行依次这些命令 1.wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.12.2-linux-x86_64.tar.gz 2.wget https://artifacts.elastic.co/downloads/…...
如何利用人工智能技术实现企业营销效率提升10倍(下)
01. AI在私域运营中可扮演重要角色 私域用户体验历程中的不满,对企业来说,无疑是一记沉重的打击。这些不满不仅会让用户感到失望和沮丧,更会在无形中侵蚀企业的各个环节,给业务带来不可估量的损失。 在私域环境中,每…...
【PHP + 代码审计】数组函数
🍬 博主介绍👨🎓 博主介绍:大家好,我是 hacker-routing ,很高兴认识大家~ ✨主攻领域:【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 🎉点赞➕评论➕收…...
Keepalive与idle监测及性能优化
Keepalive 与 idle监测 Keepalive(保活): Keepalive 是一种机制,通常用于TCP/IP网络。它的目的是确保连接双方都知道对方仍然存在并且连接是活动的。这是通过定期发送控制消息(称为keepalive消息)实现的。如果在预定时…...
DS-红黑树(RBTree)
一.红黑树 1.1 红黑树的起源 当对对AVL树做一些结构修改的操作时候,性能较为低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。 因此1972年Rudolf…...
ubuntu 如何使用阿里云盘
你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益: 了解大厂经验拥有和大厂相匹配的技术等 希望看什么,评论或者私信告诉我! 文章目录 一…...
sqlite3 交叉编译
#1.下载源码并解压 源码路径如下,下载autoconf版本 SQLite Download Page 解压 tar -zxvf sqlite-autoconf-3450200.tar.gz cd sqlite-autoconf-3450200 mkdir build # 2. 配置源代码 # 假设你已经安装了交叉编译工具链,如gcc-arm-linux-gnueabih…...
【AI生成文章】flutter ChangeNotifierProvider 实用场景举例
内容由Ai 大模型生成,不能完全保障真实 ChangeNotifierProvider 是 Flutter 中一个非常实用的工具,用于在应用程序中管理和传递状态。以下是一些实用的场景举例: 1. 用户信息管理 在应用程序中,用户信息(如用户名、…...
【0274】从shared init file或local init file加载relation cache(2 - 1)
上一篇: 【0273】深入分析 relcache(relation descriptor cache)初始化第一阶段(1) 【0264】深入分析relcache(relation descriptor cache)缓存初始化第2阶段(2) 1. 前言 本文内容是作为《【0264】深入分析relcache(relation descriptor cache)缓存初始化第2阶段…...
蓝桥杯-02-2023蓝桥杯c/c++省赛B组题目
参考 2023 年第十四届蓝桥杯 C/C B组省赛题解 2023蓝桥杯c/c省赛B组题目(最全版): A:日期统计 这题方法应该很多,没有和别人讨论想法。我的解法思路是:先 load 函数生成所有这一年的合法日期,然后枚举所有可以从数据…...
欧拉筛+并查集
集合 - 洛谷 std::vector<int> minp, primes,primes1;void sieve(int n,int p) {minp.assign(n 1, 0);primes.clear();for (int i 2; i < n; i) {if (minp[i] 0) {minp[i] i;primes.push_back(i);}for (auto p : primes) {if (i * p > n) {break;}minp[i * p]…...
《桥接模式(极简c++)》
本文章属于专栏《设计模式(极简c版)》 继续上一篇《原型模式(极简c)》。本章简要说明桥接模式。本文分为模式说明、本质思想、实践建议、代码示例四个部分。 模式说明 方案: 将抽象部分与它的实现部分分离,…...
jconsole的使用
前提 已安装jdk 使用步骤 1、命令行输入jconsole...
JavaScript详细教程
文章目录 前言一、代码位置二、注释三、变量1.字符串类型2.数组3.对象(字典) 四、条件语句五、函数六、DOM模板 前言 JavaScript 是一种脚本编程语言,它可以在网页上实现复杂的功能,网页展现给你的不再是简单的静态信息࿰…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
