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];         // 哈希表,支持渐进式 rehash
    long rehashidx;       // rehash 索引,-1 表示没有进行 rehash
    unsigned 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 的性能和功能。

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

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

相关文章

快速测试electron环境是否安装成功

快速测试electron环境是否安装成功 测试代码正确运行的效果运行错误的效果v22.4.1 版本无法使用v20.15.1版本无法使用v18.20.4 版本无法使用 终极解决办法 测试代码 1.npx create-electron-app my-electron-app 2.cd my-electron-app 3.npm start 正确运行的效果 环境没问题…

Android系统设置kernel log level的方法

Android log相关文档索引: 使用ADB命令控制logcat日志本地存储功能-CSDN博客 Android系统通过属性设置来控制log输出的方案-CSDN博客 Android系统设置kernel log level的方法-CSDN博客 Android系统设置kernel log level的方法 背景 kernel log内容过多/过少会影…

oak相机使用oak官网方式标定

目录 一、depthai ROS驱动 一、depthai ROS驱动 (1)驱动下载地址:2. C 开发快速上手 — DepthAI Docs 0.3.0.0 documentation sudo apt install ./depthai_2.17.1_arm64.deb //运行 Python3 utilities/cam_test.py -mres 400 -cams rgb,m …

Wireshark 对 https 请求抓包并展示为明文

文章目录 1、目标2、环境准备3、Wireshark 基本使用4、操作步骤4.1、彻底关闭 Chrome 进程4.2、配置 SSLKEYLOGFILE [核心步骤]4.3、把文件路径配置到 Wireshark 指定位置4.4、在浏览器发起请求4.5、抓包配置4.6、过滤4.6.1、过滤域名 http.host contains "baidu.com4.6.2…

AIGC时代创意设计师从“创作”向“智作”升级

随着人工智能技术的飞速发展,AIGC(AI Generated Content,即人工智能生成内容)时代已经到来,为创意设计领域带来了前所未有的变革。在这一时代背景下,创意设计师们正经历着从传统的“创作”向“智作”的转型…

FreeRTOS 队列

队列是一种任务到任务、任务到中断、中断到任务数据交流的一种机制。在队列中可以存 储数量有限、大小固定的多个数据,队列中的每一个数据叫做队列项目,队列能够存储队列项 目的最大数量称为队列的长度,在创建队列的时候,就需要指…

html5——CSS基础选择器

目录 标签选择器 类选择器 id选择器 三种选择器优先级 标签指定式选择器 包含选择器 群组选择器 通配符选择器 Emmet语法&#xff08;扩展补充&#xff09; 标签选择器 HTML标签作为标签选择器的名称&#xff1a; <h1>…<h6>、<p>、<img/> 语…

在pycharm中使用jupyter

在pycharm中使用jupyter 前置条件&#xff1a;你的环境中应该有juptyer &#xff0c;没有的话 pip install jupyter 点击项目目录&#xff0c;右键->new->jupyter notebook 打开file settings 找到 jupyter server &#xff08;按照默认的用代理服务器就行&#xff09; P…

ollama + lobechat 搭建自己的多模型助手

背景 人工智能已经推出了快2年了&#xff0c;各种模型和插件&#xff0c;有渐渐变成熟的趋势&#xff0c;打造一个类似 hao123网站的人工智能模型入口&#xff0c;也变得有需求了。用户会去比较多个ai给出的答案&#xff0c;作为程序员想拥有一台自己的GPU服务器来为自己服务。…

react启用mobx @decorators装饰器语法

react如果没有经过配置&#xff0c;直接使用decorators装饰器语法会报错&#xff1a; Support for the experimental syntax ‘decorators’ isn’t currently enabled 因为react默认是不支持装饰器语法&#xff0c;需要做一些配置来启用装饰器语法。 step1: 在 tsconfig.js…

always块敏感列表的相关报错,

在综合的时候&#xff0c;报错如下 Synthesis synth_1 [Synth 8-91] ambiguous clock in event control ["E:/FPGA/FPGA_project/handwrite_fft/handwrite_fft.srcs/sources_1/new/reg_s2p.v":140] 猜测报错原因&#xff08;暂时没有时间寻找原因&#xff0c;后续在…

【linux】服务器卸载cuda

【linux】服务器卸载cuda 文章目录 【linux】服务器卸载cuda1、查找已安装的 CUDA 包&#xff1a;2、卸载 CUDA&#xff1a;3、删除残留文件4、更新系统的包索引&#xff1a;5、检查是否卸载干净&#xff1a; 1、查找已安装的 CUDA 包&#xff1a; dpkg -l | grep cuda2、卸载…

Unity之OpenXR+XR Interaction Toolkit实现 Gaze眼部追踪

使用 Unity OpenXR 实现Gaze眼部追踪 在虚拟现实(VR)和增强现实(AR)应用中,眼动追踪是一项强大而受欢迎的技术。它可以让开发者更好地理解用户的注意力和行为,并创造出更加沉浸和智能的体验。在本文中,我们将探讨如何使用 Unity OpenXR 实现Gaze眼部追踪功能。 Unity …

IEEE顶刊“放水”?稳居1区Top,发文扩张IF稳长,CCF推荐,审稿友好!

本周投稿推荐 SCI • 能源科学类&#xff0c;1.5-2.0&#xff08;25天来稿即录&#xff09; • CCF推荐&#xff0c;4.5-5.0&#xff08;2天见刊&#xff09; • 生物医学制药类&#xff08;2天逢投必中&#xff09; EI • 各领域沾边均可&#xff08;2天录用&#xff09…

python3读取shp数据

目录 1 介绍 1 介绍 需要tmp.shp文件和tmp.dbf文件&#xff0c;需要安装geopandas第三方库&#xff0c;python3代码如下&#xff0c; import geopandas as gpdshp_file_path "tmp.shp" shp_data gpd.read_file(shp_file_path) for index, row in shp_data.iterro…

【Neo4j】实战 (数据库技术丛书)学习笔记

Neo4j实战 (数据库技术丛书) 第1章演示了应用Neo4j作为图形数据库对改进性能和扩展性的可能性, 也讨论了对图形建模的数据如何正好适应于Neo4j数据模型,现在到了该动 手实践的时间了。第一章 概述 Neo4j将数据作为顶点和边存储(或者用Neo4j术语,节点和关系存 储)。用户被定…

DSC主备归档报错

先看一个报错&#xff1a; 2024-07-10 22:12:21.725 [ERROR] database P0000003511 T0000000000000003696 rafil_list_overlap_consecutive_check failed, rfil(DMDATA/data/DSC02/arch/ARCHIVE_LOCAL1_0x57843343_EP1_2024-07-10_20-44-40.log)->next_seq(2901) > nex…

2.54插座开口朝板内还是板外?

答&#xff1a;开口朝板内。 这样无论是安装立式插座&#xff0c;还是卧式插座&#xff0c;引脚定义都一致。并且从左往右&#xff1a;1,2,3,4

Nordic 蓝牙5产品简介

蓝牙5.0 有四个重要的新功能&#xff1a; 更高的比特率为 2 Mbps。长距离模式在 500 kbps 和 125 kbps 两个新的较低比特率下具有更好的灵敏度。通过广告扩展&#xff0c;广播能力提高了 8 倍。改进的信道选择算法 (CSA #2)&#xff0c;可以提高与其他蓝牙和非蓝牙流量的信道协…

初识C++ | 基本介绍、命名空间、输入输出、缺省函数、函数重载、引用、内联函数、nullptr

基本介绍 C的起源 1979年&#xff0c;当时的 Bjarne Stroustrup 正在⻉尔实验室从事计算机科学和软件⼯程的研究⼯作。⾯对项⽬中复杂的软件开 发任务&#xff0c;特别是模拟和操作系统的开发⼯作&#xff0c;他感受到了现有语⾔&#xff08;如C语⾔&#xff09;在表达能⼒、可…