6、Redis系统-数据结构-04-Hash

四、哈希表(Hashtable)

哈希表是一种高效的键值对数据结构,通过散列函数将键映射到表中的位置,实现快速的插入、删除和查找操作。Redis 广泛使用哈希表来实现 Hash 对象和数据库的键值存储。以下将从结构设计、哈希冲突与链式哈希、rehash、渐进式哈希和哈希触发条件等角度详细介绍 Redis 中的哈希表。

1. 结构设计

在 Redis 中,哈希表(dict)由两个哈希表数组(dictht)组成,用于实现渐进式 rehash,以应对数据量增大时的性能问题。每个哈希表数组包含一个哈希表节点(dictEntry)的指针数组。哈希表节点用于存储实际的键值对,并通过链地址法处理哈希冲突。

哈希表结构
typedef struct dictht {dictEntry **table;    // 哈希表数组unsigned long size;   // 哈希表大小unsigned long sizemask;  // 哈希表大小掩码,用于计算索引值unsigned long used;   // 哈希表中已使用的节点数量
} dictht;typedef struct dict {dictType *type;       // 类型特定函数void *privdata;       // 私有数据dictht ht[2];         // 哈希表,支持渐进式 rehashlong rehashidx;       // rehash 索引,-1 表示没有进行 rehashunsigned long iterators;  // 当前正在运行的安全迭代器数量
} dict;typedef struct dictEntry {void *key;            // 键union {void *val;        // 值uint64_t u64;     // 无符号整数值int64_t s64;      // 有符号整数值double d;         // 双精度浮点值} v;struct dictEntry *next;  // 指向下一个哈希表节点,解决冲突
} dictEntry;
主要字段介绍
  1. table:指向哈希表节点指针数组的指针。
  2. size:哈希表的大小,即哈希表数组的长度。
  3. sizemask:哈希表大小掩码,用于计算键的索引值。
  4. used:哈希表中已使用的节点数量。
  5. rehashidx:rehash 的索引,表示当前进行到哪个位置,-1 表示没有进行 rehash。
  6. type:指向哈希表类型特定函数的指针,用于实现不同类型的哈希表。
  7. privdata:指向私有数据的指针,可以用于存储与哈希表相关的额外信息。

2. 哈希冲突及链式哈希
哈希冲突

哈希冲突是指不同的键通过哈希函数计算得到相同的索引值,从而映射到哈希表数组的同一位置。哈希冲突是哈希表的一种常见问题,需要有效的处理机制来解决。

链式哈希

链式哈希是一种解决哈希冲突的常用方法。它通过链表的形式,将所有哈希值相同的键值对连接在一起。每个哈希表数组的元素(dictEntry)都指向一个链表的头节点,当发生哈希冲突时,新节点被插入到链表的头部。

typedef struct dictEntry {void *key;            // 键union {void *val;        // 值uint64_t u64;     // 无符号整数值int64_t s64;      // 有符号整数值double d;         // 双精度浮点值} v;struct dictEntry *next;  // 指向下一个哈希表节点,解决冲突
} dictEntry;
插入操作

在哈希表中插入键值对时,Redis 首先计算键的哈希值,并将其映射到哈希表数组中的一个位置。如果该位置已经有其他节点,则通过链地址法将新节点插入到链表的头部。

int dictAdd(dict *d, void *key, void *val) {dictEntry *entry = dictAddRaw(d, key);if (!entry) return DICT_ERR;dictSetVal(d, entry, val);return DICT_OK;
}
查找操作

在哈希表中查找键对应的值时,Redis 通过计算键的哈希值来确定其在哈希表数组中的位置,然后遍历链表查找对应的键值对。

dictEntry *dictFind(dict *d, const void *key) {if (d->ht[0].size == 0) return NULL;dictEntry *he = dictFindRaw(d, key);if (he == NULL) return NULL;return he;
}
删除操作

在哈希表中删除键值对时,Redis 同样通过计算键的哈希值来确定其位置,然后遍历链表找到对应的节点并将其删除。

int dictDelete(dict *d, const void *key) {if (dictSize(d) == 0) return DICT_ERR;if (dictDeleteRaw(d, key) == DICT_OK) {if (d->ht[0].used == 0) _dictReset(&d->ht[0]);return DICT_OK;}return DICT_ERR;
}
3. rehash
rehash 的必要性

随着哈希表中元素数量的增多,哈希表的负载因子(load factor)会不断增大,从而影响性能。为了维持哈希表的性能,Redis 需要进行 rehash 操作,即重新分配哈希表的大小,并将原有的键值对重新分布到新的哈希表中。

rehash 过程
  1. 初始化新哈希表:当需要进行 rehash 时,首先为哈希表分配新的哈希表数组,并调整新哈希表的大小。
  2. 迁移节点:将旧哈希表的所有节点逐个迁移到新哈希表中。迁移过程中,计算每个节点的新哈希值,并将其插入到新哈希表的对应位置。
  3. 完成 rehash:当所有节点都迁移完成后,释放旧哈希表的内存,将新哈希表替换为当前哈希表。
void _dictRehashStep(dict *d) {if (d->iterators == 0) dictRehash(d, 1);
}
4. 渐进式 rehash
渐进式 rehash 的基本思想

渐进式 rehash 的基本思想是:将哈希表的扩容操作分摊到多次操作中完成,而不是一次性完成,从而避免一次性 rehash 带来的性能问题。通过渐进式 rehash,Redis 可以在保证高效操作的同时,平滑地完成哈希表的扩容。

渐进式 rehash 的具体实现
  1. 分阶段迁移:在每次对哈希表进行增删查改操作时,逐步将旧哈希表的节点迁移到新哈希表中。每次操作迁移一部分节点,直到旧哈希表的所有节点都迁移完成。
  2. 维护 rehash 状态:在进行 rehash 过程中,Redis 维护一个 rehash 索引(rehashidx),表示当前迁移到哪个位置。每次操作完成后,rehash 索引会相应更新,直到所有节点迁移完成。
  3. 完成 rehash:当所有节点迁移完成后,释放旧哈希表的内存,将新哈希表替换为当前哈希表。
void _dictRehashStep(dict *d) {if (d->iterators == 0) dictRehash(d, 1);
}
渐进式 rehash 的优点
  1. 平滑过渡:通过逐步迁移节点,避免了一次性 rehash 带来的性能波动。
  2. 低延迟:每次操作只迁移一部分节点,保证了每次操作的时间复杂度不会过高,从而降低延迟。
5. 哈希触发条件

哈希表的 rehash 触发条件主要有两个:

  1. 负载因子:当哈希表的负载因子超过一定阈值时,触发 rehash 操作。负载因子等于已使用的节点数量除以哈希表的大小。Redis 的默认阈值是 1,当负载因子超过 1 时,会触发 rehash。
  2. 内存使用情况:当 Redis 内存使用情况达到

一定水平时,也会触发 rehash 操作,以保证内存的高效使用。

通过这两个条件,Redis 可以在适当的时候进行哈希表的 rehash,从而维持哈希表的性能和内存利用率。

结论

通过上述解析,我们可以更好地理解哈希表的设计思想和实现原理,从而在实际开发中更好地利用哈希表提供的优势。在 Redis 中,哈希表通过高效的键值对存储和渐进式 rehash 机制,实现了高性能和低延迟的操作,适用于多种场景如键值存储、缓存等。了解这些优化策略,可以帮助我们在实际应用中更好地利用 Redis 的性能和功能。

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

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

相关文章

深入源码,探究#、$号替换符的区别

在Mybatis的日常使用过程中以及在一些技术论坛上我们都能常常听到,不要使用$符号来进行SQL的编写,要使用#符号,否则会有SQL注入的风险。那么,为什么在使用$符号时会有注入的风险呢,以及#号为什么不会有风险呢&#xff…

Python结合MobileNetV2:图像识别分类系统实战

一、目录 算法模型介绍模型使用训练模型评估项目扩展 二、算法模型介绍 图像识别是计算机视觉领域的重要研究方向,它在人脸识别、物体检测、图像分类等领域有着广泛的应用。随着移动设备的普及和计算资源的限制,设计高效的图像识别算法变得尤为重要。…

设计模式-结构型-08-组合模式

文章目录 1、学校院系展示需求2、组合模式基本介绍3、组合模式示例3.1、 解决学校院系展示(透明模式1)3.2、高考的科目(透明模式2)3.3、高考的科目(安全组合模式) 4、JDK 源码分析5、注意事项和细节 1、学校…

存储过程编程-创建(CREATE PROCEDURE)、执行(EXEC)、删除(DROP PROCEDURE)

一、定义 1、存储过程是在SQL服务器上存储的已经编译过的SQL语句组。 2、存储过程分为三类:系统提供的存储过程、用户定义的存储过程和扩展存储过程 (1)系统提供的存储过程:在安装SQL Server时,系统创建了很多系统存…

AI机器人在企业拓客上常见的功能有哪些

AI机器人具备多种功能,这些功能主要基于其被设计和训练的目的。整理了一些常见的AI机器人功能: 1. 语音识别与自然语言处理: - 语音识别:将用户的语音输入转换为文本,以便机器人可以理解和处理。 - 自然语言处理…

QCC5181 歌词歌曲名多国语言显示替代QCC5125 CSR8675

QCC518X作为Qualcomm新一代蓝牙技术芯片,支持最新蓝牙协议V5.4,较QCC512X系列,它有更强大的DSP、CPU。除支持USB、I2S、SPDIF等接口外,还扩展了LE Audio功能,扩展支持AptX Lossless。以5181为例,我们还扩展…

vscode语言模式

1.背景 写vue3ts项目的时候,用到了volar插件,在单文件使用的时候,鼠标悬浮在代码上面会有智能提示; 但是最近volar插件提示被弃用了,然后我按照它的官方提示,安装了Vue-official扩展插件,但是…

Banana Pi BPI-M5 Pro 低调 SBC 采用 Rockchip RK3576 八核 Cortex-A72/A53 AIoT SoC

Banana Pi BPI-M5 Pro,也称为 Armsom Sige5,是一款面向 AIoT 市场的低调单板计算机 (SBC),由 Rockchip RK3576 八核 Cortex-A72/A53 SoC 驱动,提供Rockchip RK3588和RK3399 SoC 之间的中档产品。 该主板默认配备 16GB LPDDR4X 和…

如何大幅减少 Vue.js 中的包大小和加载时间,提升用户体验!

大家好,我是CodeQi! 一位热衷于技术分享的码仔。 你知道吗,根据Google 的一项研究,如果网站加载时间超过 3 秒,53% 的移动用户会离开该网站? 性能优化是一个经常讨论的话题,但很多开发人员并不关心提高应用的速度。 在前端开发中,优化包大小和加载时间对于提升用户体…

下一代 CLI 工具,使用Go语言用于构建令人惊叹的网络应用程序

大家好,今天给大家分享一个创新的命令行工具Gowebly CLI,它专注于使用Go语言来快速构建现代Web应用程序。 Gowebly CLI 是一款免费开源软件,有助于在后端使用 Go、在前端使用 htmx 和 hyperscript 以及最流行的 CSS 框架轻松构建令人惊叹的 W…

入门PHP就来我这(高级)15 ~ 图书删除功能

有胆量你就来跟着路老师卷起来! -- 纯干货,技术知识分享 路老师给大家分享PHP语言的知识了,旨在想让大家入门PHP,并深入了解PHP语言。 今天给大家接着上篇文章实现图书删除功能,来实现删除图书信息记录行的功能。 1 删…

高颜值官网(3):家居用品网站12个,好的创意都在这里。

hello,大家好,我是大千UI工场,本文为大家带来家居用品网站UI,供大家欣赏。

项目代码优化(1)——下单逻辑

给一个电商开发的系统排查,发现漏洞很多。很多经验不够的开发者很容易忽视的逻辑错误陷阱。在给一个项目做二次开发时候,检测到的相关经典案例。这里整理支付和产品相关的逻辑,方便后续查看。,这里进行一些简单的逻辑漏洞梳理与修…

Ubuntu 22.04 LTS 上安装 MySQL8.0.23(在线安装)

目录 在线安装MySQL 步骤1:更新软件包列表 步骤2:安装MySQL服务器 步骤3:启动MySQL服务 步骤4:检查MySQL状态 步骤5:修改密码、权限 在线安装MySQL 步骤1:更新软件包列表 在进行任何软件安装之前&a…

p9函数(1)

int Add(int x,int y) { int z0; zxy; return z; } int main() { int a10; int b20; int sumAdd(a,b); printf("%d\n",sum); return 0; } 字符串求长度 int main() { char arr1[]"bit"; char arr2[20]"###…

移动UI: 什么特征会被认为是简洁风格,用案例告诉你

什么是简洁风格,恐怕一百个人有一百个是理解,本文通过理论分析案例的方式进行探讨。 移动 UI 中的简洁风格通常具有以下几个特征: 1. 平面化设计: 简洁风格的移动 UI 善于运用平面化设计,即去除过多的阴影、渐变和立…

水冷液冷负载系统的六种基本类型

您可以选择六种基本类型的冷却系统,以满足负载的冷却需求。每个人都有其优点和缺点。本文旨在识别不同类型的冷却系统并确定它们的优缺点,以便您可以根据自己的需求做出明智的选择。 液体冷却系统有六种基本类型: 1.液对液 2.闭环干燥系统…

深度讲解 UUID/GUID 的结构、原理以及生成机制

目录 一. 前言 二. 被广泛使用 三. UUID 的结构 3.1. 必须了解的 3.2. 十六进制数字字符(hexDigit) 3.3. UUID 基本结构 3.4. 类型(变体)和保留位 3.5. 版本(子类型) 3.6. 时间戳 3.7. 时钟序列 …

管理《欧盟数字服务法》交易者要求

《数字服务法》合规性 根据《数字服务法》(DSA) 的要求,对于在欧盟地区 (EU) 通过 App Store 分发 App 的所有交易商,Apple 需要验证并显示其联系信息。请指明你是否将以交易商或非交易商的身份在欧盟地区分发任何内容。进一步了解你是否应为交易商。 …

[激光原理与应用-101]:南京科耐激光-激光焊接-焊中检测-智能制程监测系统IPM介绍 - 5 - 3C行业应用 - 电子布局类型

目录 前言: 一、激光在3C行业的应用概述 1.1 概述 1.2 激光焊接在3C-电子行业应用 二、3C电子行业中激光焊接 2.1 纽扣电池 2.2 均温板 2.3 指纹识别器 2.4 摄像头模组 2.5 IC芯片切割 三、3C行业中激光切割 四、激光在3C行业中的其他应用 4.1 涂层去除…