当前位置: 首页 > news >正文

Java进阶—哈希冲突的解决

1. 什么是哈希冲突

哈希函数:哈希函数是一种将输入数据(键)映射到固定大小范围的输出值(哈希值)的函数。哈希函数通常用于存储 数据存储和检索领域,例如哈希表中。
哈希表:哈希表(Hash Table),也成为哈希映射(Hash Map)或字典(Dictionary),是一种常见的数据结构,用于实现关联数组,它可以将键映(Key)射到值(Value)
哈希冲突:由于哈希算法被计算的数据是无限的,而计算后的结果范围有限,当两个或更多键被哈希函数映射到相同的索引位置时,就会发生哈希冲突。

在Java中,哈希函数通常是通过hashCode()方法实现的,hashCode()方法是Object类中定义的一个方法,因此所有Java类都可以使用它。hashCode()方法的默认实现是返回对象的内存地址的哈希码,但通常情况下,我们需要重写这个方法来提供更合适的哈希函数。

在重写hashCode()方法时,通常需要遵循以下法则

  1. 相等的对象必须具有相等哈希码。也就是说,如果两个对象通过‘equals()’方法相等,那么它们的哈希码应该相等。

如果 a.equals(b)==true,它们的hashCode一定相同吗?
哈希表中,首先会比较对象的‘hashCode’值确定它们所在的桶(bucket),然后再根据‘equals()’方法来精确匹配对象。
所以在Java中a.equals(b)==true,它们的hashCode一定是相同的。除非重写了equals方法或hashCode方法()
因此,为了保持一致性,在重写equals()方法时通常也需要重写hashCode()方法,以确保相等的对象具有相同的哈希码。

  1. 尽量减少哈希冲突,即不同的对象尽量产生不同的哈希码,以提高哈希表等数据结构的性能。
    例如,对于一个简单的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 开放寻址法

在发生哈希冲突时,通过一定的方法(如线性探测、二次探测、双重哈希等)寻找下一个空的哈希表位置,键冲突的键值对放置在新的位置上。这样可以避免使用额外的数据结构存储冲突的键值对,节省空间。但是,如果哈希表填充率过高,开放寻址法的性能可能会收到影响。

  1. 线性探测(Linear Probing):在发生哈希冲突时,线性探测会顺序地检查哈希表中的下一个位置,直到找到一个空闲的位置为止。具体地说,如果哈希表中位置‘i’发生了冲突,那么线性探索位置‘i+1’,‘i+2’,直到找到一个空闲位置或遍历完整个哈希表。正常不会发生找不到位置,通常情况下,但哈希表中元素达到一定阈值,会自动触发扩容操作。

    当哈希表使用线性探测解决哈希冲突时,插入和查找元素的过程如下:
    1. 插入元素时,如果计算出的哈希位置已经被占用,则向后顺序查找直到找到一个空闲位置。

    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"}
      }
  2. 二次探测(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"}
    }
  3. 双重哈希(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 建议和注意事项

  1. 选择合适的哈希函数: 哈希函数的选择对于减少哈希冲突非常重要。一个好的哈希函数应该能够将键均匀地分布到哈希表中的槽位上,减少碰撞的概率。
  2. 考虑装载因子: 装载因子是指哈希表中已存储键值对的数量与哈希表总容量的比值。当装载因子过高时,哈希冲突的概率会增加,影响性能。因此,定期调整哈希表的大小,以保持适当的装载因子是很重要的。
  3. 选择合适的解决冲突方法: 根据应用场景和性能需求,选择合适的解决哈希冲突的方法。例如,开放寻址法适用于空间紧张的情况,而链表法适用于处理大量哈希冲突的情况和频繁插入删除的操作。
  4. 考虑并发情况: 在并发环境下,需要考虑多线程同时访问哈希表可能引发的问题。可以使用线程安全的哈希表实现或者在访问哈希表时进行适当的同步操作来处理并发访问问题。

相关文章:

Java进阶—哈希冲突的解决

1. 什么是哈希冲突 哈希函数&#xff1a;哈希函数是一种将输入数据(键)映射到固定大小范围的输出值(哈希值)的函数。哈希函数通常用于存储 数据存储和检索领域&#xff0c;例如哈希表中。 哈希表&#xff1a;哈希表(Hash Table)&#xff0c;也成为哈希映射(Hash Map)或字典&…...

css的border详解

CSS的border属性是一个简写属性&#xff0c;用于设置以下四个边框属性&#xff1a; border-width&#xff1a;定义边框的宽度。可以使用具体的像素值&#xff0c;或者使用预定义的关键字如thin、medium和thick。border-width不支持百分比值。默认情况下&#xff0c;边框的宽度是…...

如何保障消息一定能发送到RabbitMQ?

我们知道&#xff0c;RabbitMQ的消息最终是存储在Queue上的&#xff0c;而在Queue之前还要经过Exchange&#xff0c;那么这个过程中就有两个地方可能导致消息丢失。第一个是Producer到Exchange的过程&#xff0c;第二个是Exchange到Queue的过程。 为了解决这个问题&#xff0c…...

【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镜像复制与常见命令

一、前言 最近通过阿里的镜像仓库远程拉取镜像&#xff0c;发现以前的版本不见了&#xff0c;拉取了最新的镜像&#xff0c;有发现版本不配问题。那么想使用老版本的镜像那就要从别的环境获取。于是就需要进行离线镜像复制&#xff0c;打包&#xff0c;上传&#xff0c;重新导入…...

如何在linux环境上部署单机ES(以8.12.2版本为例)

ES安装&#xff08;以8.12.2版本为例&#xff09; 首先创建好对应的文件夹然后在对应的文件夹下执行依次这些命令 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在私域运营中可扮演重要角色 私域用户体验历程中的不满&#xff0c;对企业来说&#xff0c;无疑是一记沉重的打击。这些不满不仅会让用户感到失望和沮丧&#xff0c;更会在无形中侵蚀企业的各个环节&#xff0c;给业务带来不可估量的损失。 在私域环境中&#xff0c;每…...

【PHP + 代码审计】数组函数

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…...

Keepalive与idle监测及性能优化

Keepalive 与 idle监测 Keepalive&#xff08;保活&#xff09;: Keepalive 是一种机制&#xff0c;通常用于TCP/IP网络。它的目的是确保连接双方都知道对方仍然存在并且连接是活动的。这是通过定期发送控制消息&#xff08;称为keepalive消息&#xff09;实现的。如果在预定时…...

DS-红黑树(RBTree)

一.红黑树 1.1 红黑树的起源 当对对AVL树做一些结构修改的操作时候&#xff0c;性能较为低下&#xff0c;比如&#xff1a;插入时要维护其绝对平衡&#xff0c;旋转的次数比较多&#xff0c;更差的是在删除时&#xff0c;有可能一直要让旋转持续到根的位置。 因此1972年Rudolf…...

ubuntu 如何使用阿里云盘

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…...

sqlite3 交叉编译

#1.下载源码并解压 源码路径如下&#xff0c;下载autoconf版本 SQLite Download Page 解压 tar -zxvf sqlite-autoconf-3450200.tar.gz cd sqlite-autoconf-3450200 mkdir build # 2. 配置源代码 # 假设你已经安装了交叉编译工具链&#xff0c;如gcc-arm-linux-gnueabih…...

【AI生成文章】flutter ChangeNotifierProvider 实用场景举例

内容由Ai 大模型生成&#xff0c;不能完全保障真实 ChangeNotifierProvider 是 Flutter 中一个非常实用的工具&#xff0c;用于在应用程序中管理和传递状态。以下是一些实用的场景举例&#xff1a; 1. 用户信息管理 在应用程序中&#xff0c;用户信息&#xff08;如用户名、…...

【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组题目(最全版)&#xff1a; A&#xff1a;日期统计 这题方法应该很多&#xff0c;没有和别人讨论想法。我的解法思路是&#xff1a;先 load 函数生成所有这一年的合法日期&#xff0c;然后枚举所有可以从数据…...

欧拉筛+并查集

集合 - 洛谷 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++)》

本文章属于专栏《设计模式&#xff08;极简c版&#xff09;》 继续上一篇《原型模式&#xff08;极简c&#xff09;》。本章简要说明桥接模式。本文分为模式说明、本质思想、实践建议、代码示例四个部分。 模式说明 方案&#xff1a; 将抽象部分与它的实现部分分离&#xff0c…...

jconsole的使用

前提 已安装jdk 使用步骤 1、命令行输入jconsole...

JavaScript详细教程

文章目录 前言一、代码位置二、注释三、变量1.字符串类型2.数组3.对象&#xff08;字典&#xff09; 四、条件语句五、函数六、DOM模板 前言 JavaScript 是一种脚本编程语言&#xff0c;它可以在网页上实现复杂的功能&#xff0c;网页展现给你的不再是简单的静态信息&#xff0…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、切换节点 3&#xff09;、切换到 apparmor 的目录 4&#xff09;、执行 apparmor 策略模块 5&#xff09;、修改 pod 文件 6&#xff09;、…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

3403. 从盒子中找出字典序最大的字符串 I

3403. 从盒子中找出字典序最大的字符串 I 题目链接&#xff1a;3403. 从盒子中找出字典序最大的字符串 I 代码如下&#xff1a; class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...