Redis底层数据结构之字典(Dict)

news2025/7/28 2:50:01

Dict基本结构

        Dict我们可以想象成目录,要翻看什么内容,直接通过目录能找到页数,翻过去看。如果没有目录,我们需要一页一页往后翻,这样时间复杂度就与遍历的O(n)一样了,而用了Dict我们就可以在O(1)的时间复杂度内快速找到键对应的值。说到这里,大家会觉得Dict与JAVA中的哈希表功能差不多,其实,Redis的Dict数据结构底层实现正是哈希表,不过维护了2个哈希表。Redis实现Dict数据结构创建了三个重要的结构体,分别是dict、dictht和dictEntry。下面先给出Dict的整体结构帮助大家更好的理解一下:

dict

typedef struct dict{
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx;
    unsigned long iterators;
} dict;

        ht[2]:表示在一个Dict结构中,包含有两个dictht的结构,也就是我们说的两张哈希表。

        rehashidx:是dict在rehash时的偏移索引,具体如何工作在后边的rehash过程中会详细讲。

dictht

typedef struct dictht{
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
}dictht

        **table:指向实际hash存储。存储可以看做是一个数组,所以是*table表示。(源码中的**table是一个二级指针,也就是指向dictEntry*的指针)。

        size:哈希表的大小。实际就是dictEntry有多少元素空间。

        sizemask:哈希表大小的掩码表示,总是等于size-1.这个属性和哈希值一起决定一个键应该被放到table数组的哪个索引上面,索引计算规则是index=hash&sizemask,前提是size的大小是二次方幂,这一点与JAVA哈希表底层计算索引是一样的原理。

        used:表示已经使用的节点数量。通过这个字段可以很方便地查询到目前dict元素总量。

dictEntry

typedef struct dictEntry{
     void *key;
     union{
          void *val;
          uint64_tu64;
          int64_ts64;
     }v;
     struct dictEntry *next;
}dictEntry

        *key:存储键。

        v:用来存储具体的值,可以看到,值可以是一个指针,可以是uint64_t整数,也可以是int64_t整数。

        *next:用于采用拉链法将相同索引的dictEntry串起来,解决哈希冲突问题。(采用的是头插法,JAVA中JDK8之后采用的是尾插法,留个小问题,为什么JAVA中不延续使用头插法?)

Dict的渐进式扩容机制

        想必大家有一个疑问,为什么Dict底层要维护两张哈希表,实际存储的话使用一张哈希表不就可以了吗。其实,第二张哈希表的存在是为了给第一张哈希表的扩容提供支持。下面我们来详细介绍一下Dict中哈希表的渐进式扩容流程和扩容时机。

Dict渐进式扩容流程

        首先,当向字典添加新元素时,发现第一张哈希表ht[0]需要扩容,就会进行rehash操作,为第二张哈希表ht[1]分配空间。ht[1]表的大小为大于等于ht[0]表used值的2倍的2次方幂。举个例子,如果ht[0]中已经使用的节点数量为500,那么扩容时ht[1]被分配的空间是1024而不是1000。这么做是为了维护扩容后表的大小始终是2次方幂。

        接着,dict的rehashidx由静默状态(-1)变为开始工作状态(0)。

        最后,迁移ht[0]中的数据到ht[1],也就是将数据从旧表中迁移到新表中。在rehash进行期间,每次对dict执行增删改查操作,程序会顺带迁移当前rehashidx在ht[0]上对应的数据,并更新偏移索引。与此同时,部分情况周期函数也会进行迁移。如果rehashidx刚好在一个已删除的空位置上,那么是直接返回还是尝试往下找?我们来看一下dictRehash函数的源码:

//int empty_visits = n*10;//Max number of empty buckets to visit.

while(d->ht[0].table[d->rehashidx] == NULL) {
    d->rehashidx++;
    if (--empty_visits == 0) return 1;
}

        可以看到,答案是会继续往下去找,但是有个上限是n*10,即最多再找这么多次,n是传进来的参数,调用的时候实际值为1,即最多往后再找10个,这么做是防止因为连续碰到空位置导致主线程操作被阻塞。

        随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash到ht[1],此时再将ht[1]和ht[0]指针对象互换,同时将rehashidx设置为-1,表示rehash工作已经完成。这个事情也是在rehash函数做的,每次迁移完一个元素,会检查是否已经完成了整个迁移:

if (d->ht[0].used == 0) {
    zfree(d->ht[0].table);
    d->ht[0] = d->ht[1];
    _dictReset(&d->ht[1]);
    d->rehashidx = -1;
    return 0;
}

        总结一下,渐进式扩容的核心就是删改查操作时顺带迁移,其中增的操作直接增到新表中。

Dict渐进式扩容时机

        Redis提出了一个负载因子的概念(与JAVA中的负载因子不同),用于表示目前Dict的使用情况,是情况良好还是已经堵塞不堪。设负载椅子为F,那么负载因子计算公式为F=ht[0].used/ht[0].size,也就是使用空间大小和总空间大小的比值。Redis会根据负载因子的情况来进行扩容。

        当负载因子的值小于1时,认为dict的使用情况良好,不需要进行扩容。

        当负载因子的值大于等于1时,说明此时的dict空间已经非常紧张了,新增的数据会发生哈希冲突在链表上堆叠。如果此时服务器没有执行BGSAVE或者BGREWRITEAOF这两个复制命令,就会立刻进行扩容,反之则不会立刻扩容。

        当负载因子的值大于5时,说明此时的dict中哈希冲突已经非常严重了,哈希表的搜索性能严重退化向链表。此时不管服务器是否在执行复制命令,都会立刻对哈希表进行扩容操作。

Dict为什么采用渐进式扩容机制?

        在JAVA中,哈希表其实也有扩容操作,并且是在单张表上完成的rehash操作。但是对于Redis中的Dict来说,两者存放的数据量不在一个量级上,由于Redis是单线程的,如果对dict中存放的大量数据进行一次性rehash,那么耗费的时间会非常久,从而造成主线程的长时间阻塞。为了性能考虑,Dict采用空间换时间的方法,多花费一张表的空间,配合渐进式扩容机制,几乎完全消除rehash可能造成的主线程阻塞。

Dict的渐进式缩容机制

        扩容是数据太多装不下,那么对应的缩容就是空间太富裕造成了浪费。缩容的过程其实和扩容是相似的,也是渐进式缩容,这里就不详细展开了。

        同样的,Redis也通过负载因子来控制什么时候缩容:

        当负载因子大于等于0.1时,认为dict的空间合适,不需要进行缩容。

        当负载因子小于0.1时,认为dict的空间太大造成浪费,进行缩容。ht[1]的大小为第一个大于等于ht[0]中used值的2次方幂(最小为4,如果已经是4了那就保持不变)。

        同样的,如果有BGSAVE或者BGREWRITEAOF这两个复制操作正在执行,缩容也会受影响,不会进行。

总结

        Dict数据结构提供了快速索引数据的能力,其结构的设计和渐进式扩容的设计很值得大家学习。

        大家如果有什么问题或勘误,欢迎评论区留言,笔者看到了都会回复的。

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

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

相关文章

佰力博科技与您探讨低温介电温谱测试仪的应用领域

低温介电温谱测试应用领域有如下: 一、电子材料: 低温介电温谱测试仪广泛应用于电子材料的性能测试,如陶瓷材料、半导体材料、压电材料等。通过该设备,可以评估材料在高温或低温环境下的介电性能,为材料的优化和应用提…

【办公类-48-04】202506每月电子屏台账汇总成docx-5(问卷星下载5月范围内容,自动获取excel文件名,并转移处理)

背景需求: 1-4月电子屏表格,都是用这个代码将EXCEL数据整理成分类成3个WORD表格。 【办公类-48-04】20250118每月电子屏台账汇总成docx-4(提取EXCLE里面1月份的内容,自制月份文件夹)-CSDN博客文章浏览阅读1.2k次&…

基于 ZYNQ UltraScale+ OV5640的高速图像传输系统设计,支持国产替代

引 言 随着电子信息技术的不断进步,人工智能、医 疗器械、机器视觉等领域都在高速发展 [1] ,工业相机 是机器视觉系统中的一部分 [2] ,对工业相机而言,传 输图像的速率、传输过程的抗干扰能力是其关键, 工业相…

demo_win10配置WSL、DockerDesktop环境,本地部署Dify,ngrok公网测试

win10配置WSL、DockerDesktop环境,本地部署Dify,ngrok分享测试 一、配置WSL 1.1 开启Hyper-V 安装WSL2首先要保证操作系统可以开启hyper-v功能,默认支持开启hyper-v的版本为:Windows11企业版、专业版或教育版,而家庭版是不支持…

TablePlus:一个跨平台的数据库管理工具

TablePlus 是一款现代化的跨平台(Window、Linux、macOS、iOS)数据库管理工具,提供直观的界面和强大的功能,可以帮助用户轻松管理和操作数据库。 TablePlus 免费版可以永久使用,但是只能同时打开 2 个连接窗口&#xff…

SQL Indexes(索引)

目录 Indexes Using Clustered Indexes Using Nonclustered Indexes Declaring Indexes Using Indexes Finding Rows Without Indexes Finding Rows in a Heap with a Nonclustered Index Finding Rows in a Clustered Index Finding Rows in a Clustered Index with …

Axure 基础入门

目录 认识产品经理 项目团队* 基本概述 认识产品经理 A公司产品经理 B公司产品经理 C公司产品经理 D公司产品经理 产品经理工作范围 产品经理工作流程* 产品经理的职责 产品经理的分类 产品经理能力要求 产品工具 产品体验报告 原型设计介绍 原型设计概述 为…

结构型设计模式之Decorator(装饰器)

结构型设计模式之Decorator(装饰器) 前言: 本案例通过李四举例,不改变源代码的情况下 对“才艺”进行增强。 摘要: 摘要: 装饰器模式是一种结构型设计模式,允许动态地为对象添加功能而不改变其…

HCIP-Datacom Core Technology V1.0_3 OSPF基础

动态路由协议简介 静态路由相比较动态路由有什么优点呢。 静态路由协议,当网络发生故障或者网络拓扑发生变更,它需要管理员手工配置去干预静态路由配置,但是动态路由协议,它能够及时自己感应网络拓扑变化,不路由选择…

工作自动化——工作自动提炼--智能编程——仙盟创梦IDE

工作自动化中的自动提炼、自动比对代码生成日志,为软件开发与项目管理带来诸多好处。 自动提炼能从复杂代码中精准提取关键信息,节省人工梳理时间,开发人员可快速把握核心逻辑,加速项目熟悉进程。自动比对代码则及时发现版本间差异…

大语言模型评测体系全解析(上篇):基础框架与综合评测平台

文章目录 一、评测体系的历史演进与技术底座(一)发展历程:从单任务到全维度评测1. 2018年前:单数据集时代的萌芽2. 2019-2023年:多任务基准的爆发式增长3. 2024年至今:动态化、场景化、多模态体系成型关键节…

生产环境MYSQL常见锁表场景

前言 锁表是我们在生产环境十分常见的问题之一,解决问题前需要先了解锁表产生的原因以找到解决方案,并制定方案以预防锁表,本文接下来会分别模拟元数据锁表(MDL锁)、行锁升级为表锁、死锁、**显示锁表 **四种锁表情形…

结构性设计模式之Composite(组合)

结构性设计模式之Composite(组合) 摘要: Composite(组合)模式通过树形结构表示"部分-整体"层次关系,使得用户能够统一处理单个对象和组合对象。该模式包含Component(组件接口&#x…

Java面试八股--04-MySQL

致谢:感谢整理!2025年 Java 面试八股文(20w字)_java面试八股文-CSDN博客 目录 1、Select语句完整的执行顺序 2、MySQL事务 3、MyISAM和InnoDB的区别 4、悲观锁和乐观锁怎么实现 5、聚簇索引与非聚簇索引区别 6、什么情况下my…

设计模式——访问者设计模式(行为型)

摘要 访问者设计模式是一种行为型设计模式,它将数据结构与作用于结构上的操作解耦,允许在不修改数据结构的前提下增加新的操作行为。该模式包含关键角色如元素接口、具体元素类、访问者接口和具体访问者类。通过访问者模式,可以在不改变对象…

实验设计与分析(第6版,Montgomery著,傅珏生译) 第10章拟合回归模型10.9节思考题10.1 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第10章拟合回归模型10.9节思考题10.1 R语言解题。主要涉及线性回归、回归的显著性、回归系数的置信区间。 vial <- seq(1, 10, 1) Viscosity <- c(160,171,175,182,184,181,188,19…

LeetCode 高频 SQL 50 题(基础版) 之 【高级查询和连接】· 下

上部分链接&#xff1a;LeetCode 高频 SQL 50 题&#xff08;基础版&#xff09; 之 【高级查询和连接】 上 题目&#xff1a;1164. 指定日期的产品价格 题解&#xff1a; select product_id,10 price from Products group by product_id having min(change_date) > 201…

机器学习——SVM

1.什么是SVM 支持向量机&#xff08;support vector machines&#xff0c;SVM&#xff09;是一种二分类模型&#xff0c;它将实例的特征向量映射为空间中的一些点&#xff0c;SVM 的目的就是想要画出一条线&#xff0c;以 “最好地” 区分这两类点&#xff0c;以至如果以后有了…

【音视频】FFmpeg 硬件(NVDIA)编码H264

FFmpeg 与x264的关系 ffmpeg软编码是使⽤x264开源项⽬&#xff0c;也就是说ffmpeg软编码H264最终是调⽤了x264开源项⽬&#xff0c;所以我们要先理解ffmpeg和x264的调⽤关系&#xff0c;这⾥我们主要关注x264_init。对于x264的参数都在 ffmpeg\libavcodec \libx264.c x264\co…

贪心算法应用:超图匹配问题详解

贪心算法应用&#xff1a;超图匹配问题详解 贪心算法在超图匹配问题中有着广泛的应用。下面我将从基础概念到具体实现&#xff0c;全面详细地讲解超图匹配问题及其贪心算法解决方案。 一、超图匹配问题基础 1. 超图基本概念 **超图&#xff08;Hypergraph&#xff09;**是普…