数据结构哈希表(散列)Hash,手写实现(图文推导)
目录
一、介绍
二、哈希数据结构
三、✍️实现哈希散列
1. 哈希碰撞💥
2. 拉链寻址⛓️
3. 开放寻址⏩
4. 合并散列
一、介绍
哈希表,也被称为散列表,是一种重要的数据结构。它通过将关键字映射到一个表中的位置来直接访问记录,以此加快查找速度。这种映射函数被称为散列函数。哈希表的历史可以追溯到上个世纪 50 年代,由美国计算机科学家拉宾·珀尔(Rabin Pearl)和罗伯特·韦伯(Robert Weiss)发明。自那时以来,哈希表已经成为了计算机科学和编程中不可或缺的一部分,广泛应用于各种领域。
二、哈希数据结构
在计算机中,数据的存储结构主要有两种:数组和链表。数组的优势是长度固定,每个下标都指向唯一的一个值,但同时也存在长度固定的缺点。哈希表则是一种介于数组和链表之间,能够动态调整大小的数据结构。
- 使用数组存放元素,都是按照顺序存放的,当需要获取某个元素的时候,则需要对数组进行遍历,获取到指定的值,时间复杂度是 O(n)。
- 哈希表的主要优点在于它可以提供快速的插入操作和查找操作,无论哈希表中含有多少条数据,插入和查找的时间复杂度都是为 O(1),这一特性使得哈希表在处理大量数据时具有很高的效率。

三、✍️实现哈希散列
源码地址:hash_table
1. 哈希碰撞💥
说明:通过模拟简单 HashMap 实现,去掉拉链寻址等设计,验证元素索引位置的碰撞。
public class HashMap01<K, V> implements Map<K, V> {private Logger logger = LoggerFactory.getLogger(HashMap01.class);private Object[] tab = new Object[8];@Overridepublic void put(K key, V value) {int idx = key.hashCode() & (tab.length - 1);tab[idx] = value;}@Overridepublic V get(K key) {int idx = key.hashCode() & (tab.length - 1);return (V) tab[idx];}
}

- HashMap01 的实现只是通过哈希计算出的下标,散列存放到固定的数组内。那么这样当发生元素下标碰撞时,原有的元素就会被新的元素替换掉,即哈希碰撞。
测试
@Test
public void test_hashMap01() {Map<String, String> map = new HashMap01<>();map.put("01", "小火龙");map.put("04", "火爆猴");logger.info("碰撞前 key:{} value:{}","01",map.get("01"));// 模拟下标碰撞map.put("09","可达鸭");map.put("12","呆呆兽");logger.info("碰撞后 key:{} value:{}","01",map.get("01"));
}

10:50:36.662 [main] INFO com.pjp.hash_table.test.HashTableTest - 碰撞前 key:01 value:小火龙
10:50:36.666 [main] INFO com.pjp.hash_table.test.HashTableTest - 碰撞后 key:01 value:呆呆兽
- 通过测试结果可以看到,碰撞前 map.get("01") 的值是 "小火龙",两次下标索引碰撞后存放的值则是 "呆呆兽"
- 这也就是使用哈希散列必须解决的一个问题,无论是在已知元素数量的情况下,通过扩容数组长度解决,还是把碰撞的元素通过链表存放,都是可以的。
2. 拉链寻址⛓️
说明:既然我们没法控制元素不碰撞,但我们可以对碰撞后的元素进行管理。比如像 HashMap 中拉链法一样,把碰撞的元素存放到链表上。这里我们就来简化实现一下。
public class HashMap02ByZipper<K, V> implements Map<K, V> {private LinkedList<Node<K, V>>[] tab = new LinkedList[8];@Overridepublic void put(K key, V value) {int idx = key.hashCode() & (tab.length - 1);if (tab[idx] == null) {tab[idx] = new LinkedList<>();tab[idx].add(new Node<>(key, value));} else {tab[idx].add(new Node<>(key, value));}}@Overridepublic V get(K key) {int idx = key.hashCode() & (tab.length - 1);for (Node<K, V> kvNode : tab[idx]) {if (key.equals(kvNode.getKey())) {return kvNode.getValue();}}return null;}static class Node<K, V> {final K key;V value;public Node(K key, V value) {this.key = key;this.value = value;}public K getKey() {return key;}public V getValue() {return value;}}
}

- 因为元素在存放到哈希桶上时,可能发生下标索引膨胀,所以这里我们把每一个元素都设定成一个 Node 节点,这些节点通过 LinkedList 链表关联,也可以通过 Node 节点构建出链表 next 元素即可。
- 那么这时候在发生元素碰撞,相同位置的元素就都被存放到链表上了,获取的时候需要对存放多个元素的链表进行遍历获取。
测试
@Test
public void test_hashMap02() {Map<String, String> map = new HashMap02ByZipper<>();map.put("01", "小火龙");map.put("04", "火爆猴");logger.info("碰撞前 key:{} value:{}","01",map.get("01"));// 模拟下标碰撞map.put("09","可达鸭");map.put("12","呆呆兽");logger.info("碰撞后 key:{} value:{}","01",map.get("01"));
}

12:19:15.505 [main] INFO com.pjp.hash_table.test.HashTableTest - 碰撞前 key:01 value:小火龙
12:19:15.509 [main] INFO com.pjp.hash_table.test.HashTableTest - 碰撞后 key:01 value:小火龙
- 前后获取 "01" 位置元素都是 "小火龙" ,元素没有被替换,因为相同索引位置的元素放到链表上去了。
3. 开放寻址⏩
说明:除了对哈希桶上碰撞的索引元素进行拉链存放,还有不引入新的额外的数据结构,只是在哈希桶上存放碰撞元素的方式。它叫开放寻址,也就是 ThreaLocal 中运用斐波那契散列+开放寻址的处理方式。
public class HashMap03ByOpenAddressing<K, V> implements Map<K, V> {private final Node<K, V>[] tab = new Node[8];@Overridepublic void put(K key, V value) {int idx = key.hashCode() & (tab.length - 1);if (tab[idx] == null) {tab[idx] = new Node<>(key, value);} else {for (int i = idx; i < tab.length; i++) {if (tab[i] == null) {tab[i] = new Node<>(key, value);break;}}}}@Overridepublic V get(K key) {int idx = key.hashCode() & (tab.length - 1);for (int i = idx; i < tab.length; i++) {// 在开放寻址法中,如果tab[i]为null,则表示该位置没有存储任何元素,因此不需要进行后续的比较操作if (tab[i] != null && tab[i].key == key) {return tab[i].value;}}return null;}static class Node<K, V> {final K key;V value;public Node(K key, V value) {this.key = key;this.value = value;}}
}

- 开放寻址的设计会对碰撞的元素,寻找哈希桶上新的位置,这个位置从当前碰撞位置开始向后寻找,直到找到空的位置存放。
- 在 ThreadLocal 的实现中会使用斐波那契散列、索引计算累加、启发式清理、探测式清理等操作,以保证尽可能少的碰撞。
测试
@Test
public void test_hashMap03() {Map<String, String> map = new HashMap03ByOpenAddressing<>();map.put("01", "小火龙");map.put("04", "火爆猴");logger.info("碰撞前 key:{} value:{}","01",map.get("01"));// 模拟下标碰撞map.put("09","可达鸭");map.put("12","呆呆兽");logger.info("碰撞后 key:{} value:{}","01",map.get("01"));
}

15:57:33.310 [main] INFO com.pjp.hash_table.test.HashTableTest - 碰撞前 key:01 value:小火龙
15:57:33.313 [main] INFO com.pjp.hash_table.test.HashTableTest - 碰撞后 key:01 value:小火龙
15:57:33.313 [main] INFO com.pjp.hash_table.test.HashTableTest - 数据结构:HashMap{tab=[null,{"key":"01","value":"小火龙"},{"key":"09","value":"可达鸭"},{"key":"12","value":"呆呆兽"},{"key":"04","value":"火爆猴"},null,null,null]}
- 通过测试结果可以看到,开放寻址对碰撞元素的寻址存放,也是可用解决哈希索引冲突问题的。
4. 合并散列
说明:合并散列是开放寻址和单独链接的混合,碰撞的节点在哈希表中链接。此算法适合固定📌分配内存的哈希桶,通过存放元素时识别哈希桶上的最大空槽位来解决合并哈希中的冲突。
public class HashMap04ByCoalescedHashing<K, V> implements Map<K, V> {private final Node<K, V>[] tab = new Node[8];@Overridepublic void put(K key, V value) {int idx = key.hashCode() & (tab.length - 1);if (tab[idx] == null) {tab[idx] = new Node<>(key, value);}int cursor = tab.length - 1;while (tab[cursor] != null && tab[cursor].key != key) {--cursor;}tab[cursor] = new Node<>(key, value);// 将被碰撞的节点指这个新节点// while 是为了处理被碰撞节点已经指向了节点,将被碰撞节点指向的节点指向新节点while (tab[idx].idxOfNext != 0) {idx = tab[idx].idxOfNext;}tab[idx].idxOfNext = cursor;}@Overridepublic V get(K key) {int idx = key.hashCode() & (tab.length - 1);while (tab[idx] != null && tab[idx].key != key) {idx = tab[idx].idxOfNext;}if (tab[idx] == null) {return null;}return tab[idx].value;}static class Node<K, V> {final K key;V value;int idxOfNext;public Node(K key, V value) {this.key = key;this.value = value;}public K getKey() {return key;}public V getValue() {return value;}public int getIdxOfNext() {return idxOfNext;}public void setIdxOfNext(int idxOfNext) {this.idxOfNext = idxOfNext;}}@Overridepublic String toString() {return "HashMap{" +"tab=" + JSON.toJSONString(tab) +'}';}
}

- 合并散列的最大目的在于将碰撞元素链接起来,避免因为需要寻找碰撞元素所发生的循环遍历。也就是A、B元素存放时发生碰撞,那么在找到A元素的时候可以很快的索引到B元素所在的位置。
同上面测试
15:57:53.650 [main] INFO com.pjp.hash_table.test.HashTableTest - 碰撞前 key:01 value:小火龙
15:57:53.654 [main] INFO com.pjp.hash_table.test.HashTableTest - 碰撞后 key:01 value:小火龙
15:57:53.654 [main] INFO com.pjp.hash_table.test.HashTableTest - 数据结构:HashMap{tab=[null,{"idxOfNext":7,"key":"01","value":"小火龙"},null,{"idxOfNext":0,"key":"12","value":"呆呆兽"},{"idxOfNext":6,"key":"04","value":"火爆猴"},{"idxOfNext":3,"key":"09","value":"可达鸭"},{"idxOfNext":0,"key":"04","value":"火爆猴"},{"idxOfNext":5,"key":"01","value":"小火龙"}]}
- 相对于直接使用开放寻址,这样的挂在链路指向的方式,可以提升索引的性能。因为在实际的数据存储上,元素的下一个位置不一定空元素,可能已经被其他元素占据,这样就增加了索引的次数。所以使用直接指向地址的方式,会更好的提高索引性能。
相关文章:
数据结构哈希表(散列)Hash,手写实现(图文推导)
目录 一、介绍 二、哈希数据结构 三、✍️实现哈希散列 1. 哈希碰撞💥 2. 拉链寻址⛓️ 3. 开放寻址⏩ 4. 合并散列 一、介绍 哈希表,也被称为散列表,是一种重要的数据结构。它通过将关键字映射到一个表中的位置来直接访问记录&#…...
【嵌入式设计】Main Memory:SPM 便签存储器 | 缓存锁定 | 读取 DRAM 内存 | DREM 猝发(Brust)
目录 0x00 便签存储器(Scratchpad memory) 0x01 缓存锁定(Cache lockdown) 0x02 读取 DRAM 内存 0x03 DREM Banking 0x04 DRAM 猝发(DRAM Burst) 0x00 便签存储器(Scratchpad memory&#…...
dameng数据库数据id decimal类型,精度丢失
问题处理 这一次也是精度丢失,但是问题呢还是不一样,这一次所有的id都被加一了,只有id字段被加一,还有的查询查出来封装成对象之后对象的id字段被减一了,数据库id字段使用的decimal(20,6)&…...
python图神经网络,注意力机制、Transformer模型、目标检测算法、强化学习等
近年来,伴随着以卷积神经网络(CNN)为代表的深度学习的快速发展,人工智能迈入了第三次发展浪潮,AI技术在各个领域中的应用越来越广泛 本文重点为:注意力机制、Transformer模型(BERT、GPT-1/2/3/…...
安装包 amd,amd64, arm,arm64 都有什么区别
现在的安装包也不省心,有各种版本都不知道怎么选。 根据你安装的环境配置。 amd: 32位X86 amd64: 64位X86 arm: 32位ARM arm64: 64位ARM amd64是X86架构的CPU,64位版。amd64又叫X86_64。主流的桌面PC&am…...
Ansible 企业实战详解
一、ansible简介1. ansible是什么2.ansible的特点ansible的架构图 二、ansible 任务执行1、ansible 任务执行模式2、ansible 执行流程3、ansible 命令执行过程 二 .Ansible安装部署1.yum安装2.ansible 程序结构3、ansible配置文件查找顺序4、ansible配置文件5.ansible自动化配置…...
云贝教育 |【技术文章】pg缓存插件介绍
一、pg_buffercache 主要作用是查看pg的共享池中缓存的对象信息 1.1 创建扩展 postgres# create extension pg_buffercache; CREATE EXTENSION 1.2 查看视图pg_buffercache postgres# \d pg_buffercacheView "public.pg_buffercache"Column | Type | Co…...
Kohana框架的安装及部署
Kohana框架的安装及部署 tipsKohana安装以及部署1、重要文件作用说明1.1 /index.php1.2 /application/bootstrap.php 2、项目结构3、路由配置3.1、隐藏项目入口的路由3.2、配置默认路由3.3、配置自定义的路由(Controller目录下的控制器)3.4、配置自定义的路由(Controller/direc…...
无重复字符的最长子串 Golang leecode_3
刚开始的思路,先不管效率,跑出来再说,然后再进行优化。然后就有了下面的暴力代码: func lengthOfLongestSubstring(s string) int {// count 用来记录当前最长子串长度var count int// flag 用来对下面两个 if 语句分流var flag …...
Vue项目的学习一
1、Vue项目里面的.js文件里面对象添加属性 例如:在对象:row,需要在对象row里面添加一个属性状态:type,使用里面的Vue.set函数 Vue.set(参数1,参数2,参数3) Vue.set(row,type,false)解析: 参数1࿱…...
k8s备份
cpu 磁盘io 往主的写,同步给备 rootk8s-etcd02:~# cat /etc/systemd/system/etcd.service [Unit] DescriptionEtcd Server Afternetwork.target Afternetwork-online.target Wantsnetwork-online.target Documentationhttps://github.com/coreos[Service] Typen…...
python自己造轮子使用
项目结构 首先,需要按照下列格式组织你的 package project (项目名称,随意,与package无关)|----package (这个才是包名)|----…...
Elastic stack8.10.4搭建、启用安全认证,启用https,TLS,SSL 安全配置详解
ELK大家应该很了解了,废话不多说开始部署 kafka在其中作为消息队列解耦和让logstash高可用 kafka和zk 的安装可以参考这篇文章 深入理解Kafka3.6.0的核心概念,搭建与使用-CSDN博客 第一步、官网下载安装包 需要 elasticsearch-8.10.4 logstash-8.…...
解决npm报错Error: error:0308010C:digital envelope routines::unsupported
解决npm报错Error: error:0308010C:digital envelope routines::unsupported。 解决办法;终端执行以下命令(windows): set NODE_OPTIONS--openssl-legacy-provider然后再执行 npm命令成功:...
高防IP是什么?有什么优势?
一.高防IP的概念 高防IP是指高防机房所提供的IP段,一种付费增值服务,主要是针对网络中的DDoS攻击进行保护。用户可以通过配置高防IP,把域名解析到高防IP上,引流攻击流量,确保源站的稳定可靠。 二.高防IP的原理 高防I…...
php费尔康框架phalcon(费尔康)框架学习笔记
phalcon(费尔康)框架学习笔记 以实例程序invo为例(invo程序放在网站根目录下的invo文件夹里,推荐php版本>5.4) 环境不支持伪静态网址时的配置 第一步: 在app\config\config.ini文件中的[application]节点内修改baseUri参数值为/invo/index.php/或…...
StartUML的基本使用
文章目录 简介和安装创建包创建类视图时序图 简介和安装 最近在学习一个项目的时候用到了StartUML来构造项目的类图和时序图 虽然vs2019有类视图,但是也不是很清晰,并没有生成uml图,但是宇宙最智能的IDE IDEA有生成uml图的功能 下面就简单介…...
飞天使-django概念之urls
urls 容易搞混的概念,域名,主机名,路由 网站模块多主机应用 不同模块解析不同的服务器ip地址 网页模块多路径应用 urlpatterns [ path(‘admin/’, admin.site.urls), path(‘’, app01views.index), path(‘movie/’, app01views.movi…...
MongoDB分片集群搭建
----前言 mongodb分片 一般用得比较少,需要较多的服务器,还有三种的角色 一般把mongodb的副本集应用得好就足够用了,可搭建多套mongodb复本集 mongodb分片技术 mongodb副本集可以解决数据备份、读性能的问题,但由于mongodb副本集是…...
modbus报文
MODBUS规约报文解析-CSDN博客...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
