Redis数据结构——字典

news2025/1/11 5:46:43

字典是一种用来保存键值对的数据结构。
在字典中,一个key与一个value相对应,字典中的key是唯一的。
在Redis中字典使用哈希表作为底层实现,用数组来表示一个哈希表,每个元素都是一对key-value

同样,在Redis中字典由三部分组成:

  • 哈希节点,保存一对key-value
  • 哈希表,用来爆粗整个哈希表以及相关信息
  • 字典用来封装哈希表

字典的实现

哈希节点

typeof struct dictEntry{
	void *key; // 键key 

	union v ;	// 值value 
	
	struct dictEntry *next; // 指向下一个哈希节点,形成链表 
} dictEntry;

哈希节点中的这三个属性没啥说的,看过HashMap源码的,应该都懂。

哈希表

哈希表的结构体:

typeof struct dictht {
	dictEntry **table; // 哈希表数组
	
	unsigned long size; //哈希表大小
	
	unsigned long sizemask; // 掩码,用来计算索引
	
	unsigned long used; // 哈希表中已使用的节点数量 
} dictht;

还是来说一下这个结构:

  • table属性是一个数组,数组中的每个元素都是指向dictEntry哈希表节点的指针
  • sizemask用来快速计算一个哈希节点的索引

字典

字典的底层实现是哈希表,字典结构体还保存着整体的信息,还字典的结构体:

typeof struct dict {
	// 哈希表
	dictht ht[2];
	
	// 类型特定函数,针对不同类型的key-value操作的一系列函数,不必关心 
	dictType *type; 
	
	// 私有数据,不必关心 
	void *private; 
	
	// rehash索引 
	int rehashidx; 
} dict;

在字典的结构体中,有两个属性比较重要:

  • dictht ht[2],这是两个哈希表。我们前面讲解了哈希表结构体dictht,一个字典中竟然封装了两个哈希表,主要用来rehash,接下来说。
  • int rehashidx 在rehas时指向旧哈希表的一个指针,关于rehash接下来就说。

关于哈希表节点、哈希表、字典的关系,看这张图梳理一下:

hash冲突

谈到哈希表,那么hash冲突是绕不开的一个点。
哈希冲突:当两个不同的key,通过哈希运算,被分配到了统一索引上,这就是哈希冲突。

那么Redis中是如何解决哈希冲突的呢?
Redis中的哈希表与Java中的HashMap的原理相似,都是通过链表法来解决key冲突。
我们知道每个链表节点dictEntry中,都有一个next属性,这个属性用来构成单向链表,把分配到同一个索引上的节点通过此属性连接起来
因为在dictEntry中只有一个next指针,所以为了性能考虑,Redis哈希表采用了头插法,时间复杂度是O(1),排在其已有节点的前面。

Redis中使用了MurmurHash算法,即使对于相同的输入,也能够保证很好的随机分布性,而且计算速度非常快。

rehash

rehash也是哈希表绕不开的一个点。
随着操作的不断进行,哈希表中的键值对数量会逐渐增多或减少,为了让哈希表的负载因子load factor(负载因子,用来衡量哈希表满的程度)维持在一个合理的范围,当哈希表中保存的键值对的数量太多或太少时,就会触发rehash,来对哈希表进行适应的扩容或收缩。

负载因子 = 哈希表中已保存的数量 / 哈希表容量

我们前文提到,在字典中定义了两个哈希表,即dictht ht[2],在正常使用时,我们是把键值对分配到第一个哈希表中,也就是ht[0]中,当rehash时,第二个哈希表ht[1]就发挥作用了。
rehash的具体过程:

  1. 首先为ht[1]分配空间,ht[1]的大小取决于原先的ht[0]中键值对的数量:
    • 如果是扩容操作,那么ht[1] 的大小是第一个大于等于ht[0].used*2的2^n。
    • 如果是收缩操作,那么ht[1]的大小是第一个等于ht[0].used的2^n
  2. ht[1]分配空间完成后,就是键值对迁移的过程了。将ht[0]中的所有键值对再次hash放到ht[1]中,rehash就是重新计算哈希值和索引值,然后放到ht[1]的指定位置上。
  3. ht[0]中的键值对全部迁移到了ht[1]之后,释放ht[0],将ht[1]作为ht[0],并在ht[1]创建一个新的空白的哈希表,留着下一次rehash使用

何时对哈希表进行扩容操作?满足以下任意一个条件

  • redis服务器目前没有执行BGSAVE(rdb持久化)命令或BGREWRITEAOF(AOF文件重写)命令,并且此时哈希表的复杂因子大于等于1.
  • 服务器正在执行BGSAVE(rdb持久化)命令或BGREWRITEAOF(AOF文件重写)命令,并且哈希表的复杂因子大于等于5

当负载因子小于0.1时,会自动进行收缩操作

渐进式rehash

刚才说了rehash的基本流程,对于第二个过程,将ht[0]中的键值对rehash到ht[1]上这一个过程,并不是一次完成的。
因为考虑到哈希表中的键值对数量过多,一次性完成可能会占用非常长的时间,由于redis是单线程的,同时还要对外提供服务,如果rehash是一次性完成的,那么在rehash期间,Redis必须停掉服务。
因此,为了避免rehash对服务器性能造成的影响,服务器并不是一次性将ht[0]中的键值对迁移到ht[1]中的,而是分多次、渐进式、慢慢地rehash到ht[1]上的

渐进式rehash的详细过程:

  1. ht[1]分配空间,字典同时持有ht[0]ht[1]两个哈希表
  2. 在字典内部维护一个索引计数器变量,就是前文提到的rehashidx,将它设置为0(即ht[0]中第一个键值对的索引),表示rehash工作开始。
  3. 在rehash期间,每次对字典进行增、删、改、查操作时,还会顺带着将ht[0]哈希表在rehashidx指向的键值对rehash到ht[1]中,当这一次操作完成后,rehashidx + 1,指向下一个需要rehash的键值对。
  4. 随着对字典的操作不断执行,最终ht[0]中所有的键值对都会迁移到ht[1]中,这时rehashidx为-1,表示rehash已经全部完成。

在渐进式rehash的过程中,对字典的新增操作,是直接在ht[1]新的哈希表上进行,而删除、查找、更新操作是在这两个哈希表上进行的
例如,当查找一个元素时,先去ht[0]上查找,如果没有找到,再去ht[1]上查找。
随着rehash的进行,ht[0]最终变成空表。

总结

在这一篇中,说了很多,大致总结一下:

  • 字典是一种存储键值对数据的结构
  • 在Redis中,字典是由哈希表实现的,字典相关的结构体有三个:
    • dictEntry哈希表中的一个节点,就是一个键值对
    • dictht哈希表,有多个dictEntry组成的数组来表示哈希表
    • dict字典,其中保存了两个哈希表ht[2],在rehash时使用
  • 哈希表的rehash操作并不是一次性完成的,而是渐进式地,伴随着每次对哈希表的操作,顺带迁移一个键值对,直至完成。

参考文章

  • 《Redis设计与实现》
  • Redis数据结构——字典 - 随心所于 - 博客园

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

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

相关文章

【笔试题心得】关于KMP在笔试中的题型

好几家都考到KMP了 问的比较多的是 next数组 , 其实KMP的相关机制我在代码随想录算法训练营第九天|KMP算法_菜鸟的Zoom之旅的博客-CSDN博客中写道过,现在在复习一下,由于next数组的定义其实会有所歧义(有些程序中会直接将前缀表作…

C++专题--标准模板库STL

c专题-标准模板库STL 1 标准模板库概述 2 序列式容器 2.1 vector 容器 2.2 deque 容器 2.3 list 容器 3 关联式容器 4 无序关联容器 5 容器适配器 5.1 STL容器适配器的种类 5.2 stack容器适配器 5.3 queue容器适配器 5.3 priority_queue容器适配器…

Mac RN环境搭建

RN ios android原生环境搭建有时候是真恶心,电脑环境不一样配置也有差异。 我已经安装官网的文档配置了ios环境 执行 npx react-nativelatest init AwesomeProject 报错 然后自己百度查呀执行 gem update --system 说是没有权限,执行失败。因为Mac…

SQL Server2019安装后使用SQL Server身份验证登录失败

错误情况 今天在电脑安装SQL Server2019和SMMS,安装过程一切顺利,但是在使用SMMS连接数据库时出现了异常。使用"Window 身份验证"登录时正常,但是如果改为使用"SQL Server 身份验证"登录时却连接失败! 解决方…

两张图搞定前端面试特别常重要的知识点:defer和async的区别

渲染引擎解析<script>的过程 <script>标签上有defer或async属性&#xff0c;脚本就会异步加载。渲染引擎遇到这一行命令&#xff0c;就会开始下载外部脚本&#xff0c;但不会等它下载和执行&#xff0c;而是直接执行后面的命令&#xff1b;默认情况是渲染引擎遇到…

微信开发之一键扫码入群的技术实现

好友将群二维码发送给机器人&#xff0c;机器人调用本接口将自动识别入群 请求URL&#xff1a; http://域名地址/scanJoinRoom 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 …

MS2692宽带低噪声放大器

MS2692 是一款宽带低噪声放大器&#xff0c;工作频率 0.45GHz  5.0GHz 。 具有高线性度、低噪声、带内增益平坦等特点。在 0.85GHz  4.0GHz 频带内&#xff0c;增益波动小于 3dB 。在 0.85GHz  5.0GHz 频带内&#xff0c;噪声系数 小于 1.2dB 。内部集成偏…

文件批量改名高手:轻松删除文件名,仅保留编号!

您是否经常需要对大量文件进行命名调整&#xff1f;是否为繁琐的手动操作而感到厌烦&#xff1f;现在&#xff0c;我们的智能批量文件改名工具为您提供了一种简单而高效的解决方案&#xff01;只需几步操作&#xff0c;您就能轻松删除原有的文件名&#xff0c;仅保留编号&#…

Oracle-如何判断字符串包含中文字符串(汉字),删除中文内容及保留中文内容

今天遇见一个问题需要将字段中包含中文字符串的筛选出来 --建表 CREATE TABLE HADOOP1.AAA ( ID VARCHAR2(255) ); --添加字段INSERT INTO HADOOP1.AAA(ID)VALUES(理解);....--查询表内容SELECT * FROM HADOOP1.AAA;在网上查找了一下有以下三种方式&#xff1a; 第一种&#…

新的 Python URL 解析漏洞可能导致命令执行攻击

Python URL 解析函数中的一个高严重性安全漏洞已被披露&#xff0c;该漏洞可绕过 blocklist 实现的域或协议过滤方法&#xff0c;导致任意文件读取和命令执行。 CERT 协调中心&#xff08;CERT/CC&#xff09;在周五的一份公告中说&#xff1a;当整个 URL 都以空白字符开头时&…

智慧建筑工地平台,通过信息化技术、物联网、人工智能技术,实现对施工全过程的实时监控、数据分析、智能管理和优化调控

智慧工地是指通过信息化技术、物联网、人工智能技术等手段&#xff0c;对建筑工地进行数字化、智能化、网络化升级&#xff0c;实现对施工全过程的实时监控、数据分析、智能管理和优化调控。智慧工地的建设可以提高工地的安全性、效率性和质量&#xff0c;降低施工成本&#xf…

BBS-个人博客项目完整搭建、BBS多人博客项目基本功能和需求、项目程序设计、BBS数据库表结构设计、创建BBS表模型

一、BBS-个人博客项目完整搭建 项目开发流程 一、项目分类 现在互联网公司需要开发的主流web项目一般分为两类&#xff1a;面向互联网用户&#xff0c;和公司内部管理。面向互联网用户: C(consumer)端项目 公司内部管理&#xff1a;B(business)端项目还有一类web应用&#xff…

jeecg-boot批量导入问题注意事项

现象&#xff1a; 由于批量导入数据速度很快&#xff0c; 因为数据库中的create time字段的时间可能一样&#xff0c;并且jeecg框架自带的是根据生成时间排序&#xff0c; 因此在前端翻页查询的时候&#xff0c;数据每次排序可能会不一样&#xff0c; 会出现第一页已经出现过一…

Qt读写Excel--QXlsx编译为静态库2

1、概述&#x1f954; 在使用QXlsx时由于源码文件比较多&#xff0c;如果直接加载进项目里面&#xff0c;会增加每次编译的时间&#xff1b; 直接将源码加载进项目工程中&#xff0c;会导致项目文件非常多&#xff0c;结构变得更加臃肿&#xff1b; 所以在本文中将会将QXlsx编译…

三维可视化平台有哪些?Sovit3D可视化平台怎么样?

随着社会经济的发展和数字技术的进步&#xff0c;互联网行业发展迅速。为了适应新时代社会发展的需要&#xff0c;大数据在这个社会经济发展过程中随着技术的进步而显得尤为重要。同时&#xff0c;大数据技术的快速发展进程也推动了可视化技术的飞速发展&#xff0c;国内外各类…

vue中封装自动计算比例滑块

此插件为另一位漂亮的前端同事小姐姐封装,觉得非常好用于是决定记载下来,便于复用 如图需要动态传入需要分配权重的数组,平均分配可以自动将100%平均分给数组中的值 如果手动拖拽,则会自动计算可拖动最大区域,便于最终总权重必定为100% <el-alert class"merge-alert&…

【深度学习 | 梯度那些事】 梯度爆炸或消失导致的模型收敛困难?挑战与解决方案一览, 确定不来看看?

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

minio 分布式文件系统主从复制

1. 在slave节点下载mc客户端工具 下载 2. 移动或复制mc文件到/usr/local/bin/文件夹 mv mc /usr/local/bin 3. 赋值权限给 mc 文件 chmod x mc 4. 查看mc客户端版本,看是否可用 #控制台显示返回信息用下面这条 mc --version #控制台显示无返回信息用下面这条 mc --versi…

LeetCode ACM模式——二叉树篇(一)

刷题顺序及思路来源于代码随想录&#xff0c;网站地址&#xff1a;https://programmercarl.com 目录 定义二叉树 创建二叉树 利用前序遍历创建二叉树 利用数组创建二叉树 打印二叉树 144. 二叉树的前序遍历 递归遍历 迭代遍历&#xff08;利用栈&#xff09; 145. 二…

移动端预览指定链接的pdf文件流

场景 直接展示外部系统返回的获取文件流时出现了跨域问题&#xff1a; 解决办法 1. 外部系统返回的请求头中调整&#xff08;但是其他系统不会给你改的&#xff09; 2. 我们系统后台获取文件流并转为新的文件流提供给前端 /** 获取传入url文件流 */ GetMapping("/get…