【手写数据库内核组件】0201 哈希表hashtable的实战演练,多种非加密算法,hash桶的冲突处理,查找插入删除操作的代码实现

hash表原理与实战

专栏内容

  • postgresql使用入门基础
  • 手写数据库toadb
  • 并发编程

个人主页:我的主页
管理社区:开源数据库
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.

文章目录

  • hash表原理与实战
  • 一、概述
  • 二、hash表整体介绍
    • 2.1 hash表的应用场景
    • 2.2 整体架构
  • 三、hash算法选择
  • 四、hash表操作
    • 4.1 冲突处理
    • 4.2 查找操作
    • 4.3 插入操作
    • 4.4 删除操作
  • 五、总结
  • 结尾

一、概述


hash表的应用非常广泛,在网上也可以看到分享的各种hash表的实现,都比较概念化。

本章节从实战的角度出发,以数据库内核中的应用为例,来看看hash表的原理与实现。

二、hash表整体介绍


哈希算法(Hash)又称摘要算法,它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。

哈希算法最重要的特点就是:

  • 相同的输入一定得到相同的输出;
  • 不同的输入大概率得到不同的输出;

我们想利用hash算法的这一特性,将输入的一组数据,经过hash算法计算后,输出唯一的 32位或64位的整形值key。

当我们需要找到存储的数据时,通过这个key查找,而查找整型值的效率就很高了,可以用二分法进行查找。

这样一个存储数据的结构,我们叫它hash表,也就是通常说的key-value形式的存储,它的查找效率与数据的类型无关。

2.1 hash表的应用场景

hash表一般用于存储大量的数据,而数据的类型是字符串,或者更复杂的复合类型结构体,或者是更大的数据;

直接通过原始数据进行查找时,代价非常高,将它们转换为hash 值后,就可以通过恒定的效率进行查找。

在数据库中的应用有:

  • 数据块缓存,某个数据块是否已经在缓存中,通过对数据块编号的hash值进行查找;
  • 系统字典的查找,某个表是否已经创建了,通过表的hash值进行查找;
  • hash索引,记录数据的hash值,查找时按hash值进行查找;

2.2 整体架构

hash表的实现一般由几方面组成,hash算法,bucket计算,冲突处理,key-value对应形式,以及三种操作。

在这里插入图片描述

  • 既然是一个table,那么内部基本存储结构是一个数组,数组的最大元素个数就是capacity;
  • 数组中的每个元索叫做bucket桶,来存储key-value对数据;
  • bucket位置的计算,一般会采用 hash值 % capacity 来计算;hash值一般是一个32位,64位或者128位的整数,取余后得到数组中的下标,这就是当前key-value要存储的位置;

三、hash算法选择


查找主要依赖高效的hash值的计算,一个高效,碰撞少的算法,能让hashtable的效率大大提升。

常见的hash算法有,MD5, sha-256等,这些常用于加密,而hashtable并不需要对数据进行加密,更看重计算的效率。

由此出现了一些快速hash算法,比较有名的如:

  • murmurhash3, 这是第三个版本,速度公认的非常快,开源了各种语言实现;
  • Spookyhash,这个目前支持128位;
  • cityhash,是google发布的,会利用现代CPU的特性进行性能提升,对于低于64位的输入处理比较复杂;

建议使用murmurhash3,算法简单高效,对于较少的输入也能高效处理。

这些算法都可以在github上下载得到,加入.c,.h文件后就可以直接调用使用。

类似如下调用:

seed = 123456789
data = "example data"
hash_value = murmur_hash(seed, data)

四、hash表操作


hash表的操作一般有插入,查找,删除三类基本操作。

对于修改操作可以分解为这三项的组合,先查找,再删除,然后插入,因为修改后的键值发生变化,对于它在hash表中的位置也会发生变化。

4.1 冲突处理

在开始操作之前,需要注意一种情况,因为我们数组元素个数有限,在取余之后难免会出现多个key-value数据在相同位置的情况,也就是key产生了冲突。

一般有两种处理方式:

  • 一是在冲突位置往后继续找空位置存储;
  • 二是在当前桶内以链表的形式存储;

两种不同的冲突处理,对应了后面操作的不同。这里采用第二种方法,如果有多个相同数据在同一桶中时,以单链表的形式存储。

在这里插入图片描述

图中可以看到,出现冲突时,key4,key5直接追加到key1后面。

那么定义数组元素类型时,就要定义为链表形式。

typedef unsigned long long HASHKEY; typedef struct HashElement
{struct HashElement *link;HASHKEY             hashKey;char                *value;
}HashElement;

这里定义hash为64位的整形,当然可以是其它位数。

4.2 查找操作

查找一个key-value值是否在hashtable中的步骤如下:

  • 调用hash算法接口,计算value的hash值;
  • 按找hash值计算bucket位置;
  • 找到bucket,查看是否为空;
  • 如果bucket中有多个元素,遍历链表进行比对hash值;
  • 如果存在相同的hash值元素,则找到;否则没有找到。

获取hashkey函数

#define Hash_capacity 100
HashElement * hashtable[Hash_capacity];HASHKEY getHashKey(char *value, int valueSize)
{return spooky_hash64(value, valueSize, 0);
}

获取bucket函数

int GetBucketIndex(HASHKEY key, PHashTableInfo hashTableInfo)
{int bucket = key & Hash_capacity;return bucket;
}

查找函数

HashElement* HashFindEntry(char *value)
{HashElement *entry = NULL;int bucket = 0;HASHKEY key = 0;key = getHashKey(value, strlen(value));bucket = GetBucketIndex(key);entry = GetHashEntryFromBucket(hashtable[bucket], key);return entry;
}

从bucket链中查找

HashElement* GetHashEntryFromBucket(HashElement* bucket, HASHKEY key)
{HashElement* element = bucket;while(element != NULL){if(element->hashKey == key) {return element;}element = element->link;}return NULL;
}

当然这里,除取比较key值外,还可以对value定义比较函数,这样避免hash值冲突的情况。

4.3 插入操作

插入操作就比较简单,步骤如下:

  • 计算hash 值;
  • 根据hash值获取bucket位置;
  • 存储对应bucket,如果已经有元素,存到链到头部;
HashElement* HashInsertEntry(char *value)
{HashElement *entry = NULL;int bucket = 0;HASHKEY key = 0;key = getHashKey(value, strlen(value));bucket = GetBucketIndex(key);entry = malloc(sizeof(HashElement));if(NULL == entry){return NULL;}entry->link = NULL;entry->hashKey = key;entry->value = value;if(NULL != hashtable[bucket])entry->link = hashtable[bucket];hashtable[bucket] = entry;return entry;
}

hash节点数量不确定,故采用动态内存分配;

在冲突时采用了头插法,这样操作比较简单;

4.4 删除操作

从hash表中找到并删除一个元素的步骤如下:

  • 计算value的hash值;
  • 计算对应的bucket位置
  • 从bucket链中进行查找,同时记录下它的前继;
  • 将对应key的元素从链表中删除;注意链表只有一个元素的情况;
  • 将删除的元素返回,由调用者释放内存空间;
HashElement* DeleteHashEntry(char *value)
{HashElement *pre = NULL;HashElement* element = NULL;int bucket = 0;HASHKEY key = 0;key = getHashKey(value, strlen(value));bucket = GetBucketIndex(key);pre = element = hashtable[bucket];while(element != NULL){if(element->hashKey == key) {if(pre == element){hashtable[bucket] = NULL;}else{pre->link = element->link;}return element;}pre = element;element = element->link;}return NULL;
}

五、总结


本文介绍了哈希表的实现及原理,同时介绍了几种hash计算方法。

当然本节介绍的内容,都是在没有并发冲突的情况下使用,如果多线程操作时,需要进行加锁处理。

如果需要更高效的并发场景下的hash表,后面章节会继续介绍。

结尾


非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!

作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。

注:未经同意,不得转载!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/868160.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【TB作品】51单片机 Proteus仿真 MAX7219点阵驱动数码管驱动

1、8乘8点阵模块(爱心) 数码管测试程序与仿真 实验报告: MAX7219 数码管驱动测试 一、实验目的 通过对 MAX7219 芯片的编程与控制,了解如何使用单片机驱动数码管显示数字,并掌握 SPI 通信协议的基本应用。 二、实验器材 51…

多项式求和之九(给定程序中函数 fun 的功能是:求出以下分数序列的前 n 项之和,并通过函数值返回 main 函数。)

代码 #include <stdio.h> /********found********/ double fun(int n){double a2,b1,c,s0;while(n>0){n--; /********found********/ssa/b;ca;aab;bc;}return s; } void main(){int n;scanf("%d",&n);printf("%lf\n",fun(n)); }友情提示 1、…

期末上分站——计组(5)

简答题11-21 11、为了提高计算机系统的输入/输出能力&#xff0c;可以在总线的设计与实现中采用哪些方案&#xff1f; 答&#xff1a;1. 提高总线时钟频率 2. 增加数据总线的位数 3. 采用成组数据传送&#xff08;BURST传送&#xff09;方式 4. 采用多总线结构 5. 优化总线传输…

微深节能 煤码头自动化翻堆及取料集控系统 格雷母线

微深节能格雷母线高精度位移测量系统是一种先进的工业自动化位置检测解决方案&#xff0c;它被广泛应用于煤码头自动化翻堆及取料集控系统中&#xff0c;以实现对斗轮堆取料机等大型机械设备的精准定位和自动化控制。 系统原理简述&#xff1a; 格雷母线系统的工作原理基于电磁…

如何在Spring Boot中实现数据加密

如何在Spring Boot中实现数据加密 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 一、数据加密的重要性与应用场景 在当今信息安全日益受到重视的背景下&…

软件工程需求之:业务需求与用户需求

在软件开发项目中&#xff0c;"业务需求"和"用户需求"是两个核心概念&#xff0c;它们分别从不同的角度描述了软件应该具备的功能和特性。理解这两个概念的区别对于成功地规划和开发软件至关重要。 业务需求 业务需求主要关注于软件项目如何帮助实现企业…

EOF 为 (End Of File) 的缩写 , 值通常为 -1

EOF是一个计算机术语&#xff0c;为 End Of File 的缩写 EOF 的值通常为 -1 EOF 的值通常为 -1&#xff0c;但它依系统有所不同。巨集 EOF会在编译原始码前展开实际值给预处理器。 与 feof 与 feof C语言中&#xff0c;当把数据以二进制形式存放到文件中时&#xff0c;就会有…

[AIGC] ClickHouse的表引擎介绍

ClickHouse是一种高性能的列式数据库管理系统&#xff0c;支持各种不同的表引擎。表引擎是数据库系统中的核心组件&#xff0c;它定义了数据的存储方式和访问方式。本文将介绍ClickHouse中常见的表引擎及其特点。 文章目录 一、MergeTree引擎二、ReplacingMergeTree引擎三、Sum…

阿里云ecs服务器,nginx多域名多项目部署教程,含本地部署教程

nginx多域名部署项目 本地部署线上部署 一、本地部署 第一步&#xff1a; winr 输入drivers 打开hosts文件&#xff0c;编辑 加行 127.0.0.1 自定义域名 … 第二步&#xff1a; 下载 nginx 安装好以后 打开ngin安装目录&#xff0c;选择nginx.conf 打开 #user Administ…

前端面试题10(js多位数组变一维数组)

1. 使用concat()和递归 function flatten(arr) {return arr.reduce((acc, val) > Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), []); }let multiDimArray [[1, 2, [3]], 4]; let flatArray flatten(multiDimArray); console.log(flatArray); // 输…

C++ STL IO流介绍

目录 一:IO流的继承关系: 二:输入输出功能 1. 基本用法 2. 格式化输入 3.非格式化输入 4. 格式化输出 三:流 1. 字符流 2. 向字符流中写入数据 3. 从字符流中读出数据 4. 清空字符流 5.完整的例子 四:文件流 一:IO流的继承关系: 类含义basic_streambuf 读取…

Python的`queue`模块

队列&#xff08;Queue&#xff09; 在Python的queue模块中&#xff0c;Queue类是一个线程安全的队列实现&#xff0c;用于在多线程编程中安全地交换信息。它遵循先入先出&#xff08;FIFO&#xff09;的原则。Queue类提供了几种主要的方法&#xff1a; put(item): 将一个项目…

动态规划|剑指 Offer II 093. 最长斐波那契数列

如果数组 arr 中存在三个下标 i、j、k 满足 arr[i]>arr[j]>arr[k] 且 arr[k]arr[j]arr[i]&#xff0c;则 arr[k]、arr[j] 和 arr[i] 三个元素组成一个斐波那契式子序列。由于数组 arr 严格递增&#xff0c;因此 arr[i]>arr[j]>arr[k] 等价于 i>j>k。 把这道题…

OPPO手机终极保活方案

Push应用解决CPU休眠应用锁OPPO的电源设置和应用省电设置 要保证OPPO手机的性能足够, 比如内存8G, 否则可能会因为性能不足而被杀死 为了保证长期流畅运行, 应该设置手机定时重启, 比如通过系统设置每天重启, 部分手机不重启会慢慢变卡 需要大量测试去验证方案的普遍性, 就算…

odoo文档的安装

步骤 1: 安装必要的软件 确保你已经安装了Git和Python 3.6、3.7或3.8之一。 步骤 2: 克隆 Odoo 文档存储库 打开终端&#xff0c;然后使用Git克隆Odoo的文档存储库。 git clone https://github.com/odoo/documentation.git cd documentation步骤 3: 安装 Python 依赖项 …

java基础--String字符串对象

一.掌握创建String字符串对象的两种方式 方式一(常用) 在程序中直接写字符串变量,就是一个String对象 String s1 "abc"; System.out.println(s1);注意 : 打印字符串类型的变量,是不会看到字符串对象空间地址值的,底层是有优化的,直接看到字符串对象中存储的内容…

代码随想录-DAY①-数组——leetcode 704 | 27

704 思路 定义查找的范围 [left,right]&#xff0c;初始查找范围是整个数组。每次取查找范围的中点 mid&#xff0c;比较 nums[mid] 和 target 的大小&#xff0c;如果相等则 mid 即为要寻找的下标&#xff0c;如果不相等则根据 nums[mid] 和 target 的大小关系将查找范围缩小…

【qt】TCP的监听 (设置服务器IP地址和端口号)

TCP监听是在自己的IP地址上进行的。 当一个TCP服务器程序启动时&#xff0c;它会绑定到一个特定的IP地址和一个端口号上&#xff0c;以便可以接收来自该IP地址和端口号的传入连接请求. 所以我们要先来获取主机的IP地址和设置端口号. 注意: 服务器程序无法任意设置IP地址&…

Java(七)——多态

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 ⚡开源项目&#xff1a; rich-vue3 &#xff08;基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL&#xff09; &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1…

Fortigate 飞塔防火墙CLI – 概述

1&#xff0c;可以通过SSH, Telnet, 或者serial console2&#xff0c;CLI的配置是分级的结构&#xff0c;如下所示&#xff1a;config system interfaceedit "internal"set vdom "root"set ip 192.168.100.99 255.255.255.0set allowaccess p… 1&#xff…