【手写数据库内核组件】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
如有错误或者疏漏欢迎指出,互相学习。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

下载linux的吐槽

本来这几天放假了,想下一个linux玩一玩 教程(我就是根据这个教程进行下载的,但是呢在进行修改BIOS 模式的 地方遇见了困难,也许是电脑修过的原因,我狂按F12 以及 FnF12都没有BIOS设置,只有一个让我选择用w…

第二次练习

目录 一、student表的增删改查 1.向student表中添加一条新记录 2. 向student表中添加多条新记录 3.向student表中添加一条新记录 4.更新表,grade 大于90的加0.5 5.删除成绩为空的记录 二、用户权限部分 1、创建一个用户test1使他只能本地登录拥有查询student表的权…

6、Redis系统-数据结构-05-整数

五、整数集合(Intset) 整数集合是 Redis 中 Set 对象的底层实现之一。当一个 Set 对象只包含整数值元素,并且元素数量不大时,就会使用整数集合这个数据结构作为底层实现。整数集合通过紧凑的内存布局和升级机制,实现了…

NSSCTF-Web题目24(RCE-空格绕过、过滤绕过)

目录 [MoeCTF 2021]babyRCE 1、题目 2、知识点 3、思路 [SWPUCTF 2022 新生赛]funny_web 4、题目 5、知识点 6、思路 [MoeCTF 2021]babyRCE 1、题目 2、知识点 空格绕过、过滤绕过 3、思路 出现源码,进行代码审计 需要我们GET方式上传一个rce变量&#x…

针对tcp不出网打——HTTP隧道代理(以CFS演示)

目录 上传工具到攻击机 使用说明 生成后门文件 由于电脑短路无法拖动文件,我就wget发送到目标主机tunnel.php文件​ 成功上传​ 可以访问上传的文件 启动代理监听 成功带出 后台私信获取弹药库工具reGeorg 上传工具到攻击机 使用说明 生成后门文件 pyt…

分班结果老师怎么发给家长?

分班结果老师怎么发给家长? 随着新学期的脚步渐近,老师们的工作也变得愈发繁忙。从准备教学计划到整理课程材料,每一项任务都不容小觑。而其中,分班结果的告知工作,更是让不少老师头疼不已。传统的分班通知方式&#…

fork创建子进程详解

一.前言 在上一篇文章-进程的概念,最后我们提到了创建进程的方式有两种方式,一种是手动的创建出进程,还有一种就是我们今天要学习的使用代码的方式创建出子进程-fork。 而学习fork创建出进程的过程中,我们会遇到以下问题&#x…

STL——map和set

目录 一、set 二、map 1.插入 2.隆重介绍 [] A使用场景 B原理 一、set set即STL库中提供的K模型的二叉搜索树&#xff0c;他的函数使用和其他容器很相似&#xff0c;可以自行阅读文档#include <set> 本文旨对库中难以理解的函数作说明 二、map map即KV模型的二…

触底加载的两种思路(以vue3前端和nodejs后端为例)

一:首先,nodejs后端的代码都是一样的. 需要前端返回page参数,然后nodejs逻辑进行处理,截取页数和每页条数和总条数, 总条数用来作为判断是否有数据的条件,也可以不用,注意看下文 一:不用获取容器高度的. pinia中进行的axios请求处理 在vue文件中进行pinia中数据的导入,继续进…

全面解析 TypeScript 泛型的二三事

2024年了相信大家都已经在日常开发的过程中使用上了 TypeScript 了。TypeScript 增强了代码可靠性和可维护性&#xff0c;确保减少运行时错误并提高开发人员的工作效率。 TypeScript 通过类型声明 使得 javascript 拥有了强类型校验。而泛型的是类型声明中最重要的一环&#x…

Nettyの源码分析

本篇为Netty系列的最后一篇&#xff0c;按照惯例会简单介绍一些Netty相关核心源码。 1、Netty启动源码分析 代码就使用最初的Netty服务器案例&#xff0c;在bind这一行打上断点&#xff0c;观察启动的全过程&#xff1a; 由于某些方法的调用链过深&#xff0c;节约篇幅&#xf…

Linux内核链表使用方法

简介&#xff1a; 链表是linux内核中最简单&#xff0c;同时也是应用最广泛的数据结构。内核中定义的是双向链表。 linux的链表不是将用户数据保存在链表节点中&#xff0c;而是将链表节点保存在用户数据中。linux的链表节点只有2个指针(pre和next)&#xff0c;这样的话&#x…

在Linux操作系统使用逻辑卷的快照(snapshot),进行对逻辑卷的数据备份。

作用&#xff1a;结合特定应用程序&#xff0c;方便备份数据。 基于cow&#xff08;copy on write 写时复制&#xff09;机制 在创建逻辑卷快照的时候&#xff0c;如果不去设置逻辑卷快照的权限的话&#xff0c;那么这个逻辑卷的权限就是可读可写&#xff0c; 创建逻辑卷快照…

coco数据集格式计算mAP的python脚本

目录 背景说明COCOeval 计算mAPtxt文件转换为coco json 格式自定义数据集标注 背景说明 在完成YOLOv5模型移植&#xff0c;运行在板端后&#xff0c;通常需要衡量板端运行的mAP。 一般需要两个步骤 步骤一&#xff1a;在板端批量运行得到目标检测结果&#xff0c;可保存为yol…

AI教你如何系统的学习Python

Python学习计划 第一阶段&#xff1a;Python基础&#xff08;1-2个月&#xff09; 目标&#xff1a;掌握Python的基本语法、数据类型、控制结构、函数、模块和包等。 学习Python基本语法&#xff1a;包括变量、数据类型&#xff08;整数、浮点数、字符串、列表、元组、字典、…

STM32基础篇:GPIO

GPIO简介 GPIO&#xff1a;即General Purpose Input/Output&#xff0c;通用目的输入/输出。就是一种片上外设&#xff08;内部模块&#xff09;。 对于STM32的芯片来说&#xff0c;周围有一圈引脚&#xff0c;有时需要对引脚进行读写&#xff08;读&#xff1a;从外部输入一…

【xinference】(15):在compshare上,使用docker-compose运行xinference和chatgpt-web项目,配置成功!!!

视频演示 【xinference】&#xff08;15&#xff09;&#xff1a;在compshare上&#xff0c;使用docker-compose运行xinference和chatgpt-web项目&#xff0c;配置成功&#xff01;&#xff01;&#xff01; 1&#xff0c;安装docker方法&#xff1a; #!/bin/shdistribution$(…

【嵌入式DIY实例-ESP8266篇】-LCD ST7735显示BMP280传感器数据

LCD ST7735显示BMP280传感器数据 文章目录 LCD ST7735显示BMP280传感器数据1、硬件准备与接线2、代码实现本文介绍如何将 ESP8266 NodeMCU 板 (ESP-12E) 与 Bosch Sensortec 的 BMP280 气压和温度传感器连接。 NodeMCU 微控制器 (ESP8266EX) 从 BMP280 传感器读取温度和压力值,…

VUE3初学入门-02-VUE创建项目

创建VUE项目的另一个方法 三种方法通过vue-cli进行创建通过npm进行创建比较 部署到nginx修改配置生成部署文件 三种方法 上一篇是在VSCODE中建立工作区&#xff0c;然后创建&#xff0c;属于命令加鼠标方式。个人感觉&#xff0c;在VSCODE基本上都是这样的操作&#xff0c;不是…

vue3中svg图标的封装与使用

组件封装&#xff1a; <template><svg :class"svgClass" :style"{ width: size px, height: size px, color: color, verticalAlign:deviationem}" aria-hidden"true"><use :xlink:href"#icon-${name}" /></s…