数据结构哈希表(散列)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博客...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
