redis 从0到1完整学习 (六):Hash 表数据结构

文章目录

  • 1. 引言
  • 2. redis 源码下载
  • 3. dict 数据结构
  • 4. 哈希表扩容与 rehash
  • 5. 参考


1. 引言

前情提要:
《redis 从0到1完整学习 (一):安装&初识 redis》
《redis 从0到1完整学习 (二):redis 常用命令》
《redis 从0到1完整学习 (三):redis 数据结构》
《redis 从0到1完整学习 (四):字符串 SDS 数据结构》
《redis 从0到1完整学习 (五):集合 IntSet 数据结构》
本文主要结合源码来介绍 hash 表的数据结构

2. redis 源码下载

Redis 源码可以点击这里下载,方便查看其中定义的一些数据结构。
在这里插入图片描述

3. dict 数据结构

Dict 由三部分组成,分别是:哈希表(DictHashTable)、哈希节点(DictEntry)、字典(Dict),数据结构如下:
在这里插入图片描述
dict、dictht、dictEntry 三者的数据结构关系如下:
在这里插入图片描述

当 Dict 添加键值对时,首先由 key 计算出 hash 值 h,通过 h & sizemask (等同取模计算,提升计算速度) 计算元素对应数组中的索引位置。假设哈希值 h = 5,则 5&7=5,因此键值对存储到数组索引为5的位置。

如下图:第一次插入到下标为5的数组中。
在这里插入图片描述

如果第二次插入的 hash 值计算后的下标也是5,则第二次插入到链表的头部:
在这里插入图片描述

4. 哈希表扩容与 rehash

当哈希表中元素越来越多导致哈希冲突增多时,链表过长后,会查询效率降低,由查询的时间复杂度最开始的 O(1) 向 O(n) 移动。

这部分源码在 Redis 的好几个版本都有所变化,主要是看看扩容的条件:
(1)Redis 6.0
这里是直接判断 ht->used == ht->size

/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *ht) {
    /* If the hash table is empty expand it to the initial size,
     * if the table is "full" dobule its size. */
    if (ht->size == 0)
        return dictExpand(ht, DICT_HT_INITIAL_SIZE);
    if (ht->used == ht->size)
        return dictExpand(ht, ht->size*2);
    return DICT_OK;
}

(2)Redis 6.2
引入哈希表的负载因子:LoadFactor = used/size。在每次新增键值对时都会检查负载因子。

/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
    // 已经在 rehash, 则返回
    if (dictIsRehashing(d)) return DICT_OK;

    // 如果为空,则初始化 size 为4
    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);

    // 如果负载因子 > dict_force_resize_ratio(定义为5),则扩容
    if (d->ht[0].used >= d->ht[0].size &&
        (dict_can_resize ||
         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio) &&
        dictTypeExpandAllowed(d))
    {
        return dictExpand(d, d->ht[0].used + 1);
    }
    return DICT_OK;
}

(3)Redis 7.2

/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
    /* Incremental rehashing already in progress. Return. */
    if (dictIsRehashing(d)) return DICT_OK;

    /* If the hash table is empty expand it to the initial size. */
    if (DICTHT_SIZE(d->ht_size_exp[0]) == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);

    /* If we reached the 1:1 ratio, and we are allowed to resize the hash
     * table (global setting) or we should avoid it but the ratio between
     * elements/buckets is over the "safe" threshold, we resize doubling
     * the number of buckets. */
    if (!dictTypeExpandAllowed(d))
        return DICT_OK;
    if ((dict_can_resize == DICT_RESIZE_ENABLE &&
         d->ht_used[0] >= DICTHT_SIZE(d->ht_size_exp[0])) ||
        (dict_can_resize != DICT_RESIZE_FORBID &&
         d->ht_used[0] / DICTHT_SIZE(d->ht_size_exp[0]) > dict_force_resize_ratio))
    {
        return dictExpand(d, d->ht_used[0] + 1);
    }
    return DICT_OK;
}

下面以 Redis 6.2 源码介绍下扩容的核心方法_dictExpand

/* Expand or create the hash table,
 * when malloc_failed is non-NULL, it'll avoid panic if malloc fails (in which case it'll be set to 1).
 * Returns DICT_OK if expand was performed, and DICT_ERR if skipped. */
int _dictExpand(dict *d, unsigned long size, int* malloc_failed)
{
    if (malloc_failed) *malloc_failed = 0;
    // 如果当前 size 大于要申请的 size,或者正在 rehash,则报错
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;
    dictht n; /* the new hash table */
    // 初始化第一个大于等于 size 的 2^n 数,这个数赋值为 realsize,但是不会低于4
    unsigned long realsize = _dictNextPower(size);
    ...
	
    // 重置 hash 表的大小和掩码,并且分配新内存
    n.size = realsize;
    n.sizemask = realsize-1;
    if (malloc_failed) {
        n.table = ztrycalloc(realsize*sizeof(dictEntry*));
        *malloc_failed = n.table == NULL;
        if (*malloc_failed)
            return DICT_ERR;
    } else
        n.table = zcalloc(realsize*sizeof(dictEntry*));

    n.used = 0;

    // 第一次初始化,则直接返回
    if (d->ht[0].table == NULL) {
        d->ht[0] = n;
        return DICT_OK;
    }
	// 如果不是第一次初始化,说明是扩容,需要 rehash,将 rehashidx 置为0,在后续增删改,会触发 rehash
    d->ht[1] = n;
    d->rehashidx = 0;
    return DICT_OK;
}

上面代码的最后只是说明了要进行 rehash 操作,在 rehash 过程中:

  • 每次增、删、改、查,都会把 dict.ht[0].table[rehashidx] 的值 rehash 到 dict.ht[1] 中,同时 rehashidx++,这样渐进式地 rehash,防止 rehash 阻塞主进程太久,影响效率。
  • 新增操作,直接写入ht[1]
  • 删、改、查会在 dict.ht[0],dict.ht[1] 依次查找。

5. 参考

《redis 从0到1完整学习 (一):安装&初识 redis》
《redis 从0到1完整学习 (二):redis 常用命令》
《redis 从0到1完整学习 (三):redis 数据结构》
《redis 从0到1完整学习 (四):字符串 SDS 数据结构》
《redis 从0到1完整学习 (五):集合 IntSet 数据结构》

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

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

相关文章

循环渲染ForEach

目录 1、接口说明 2、键值生成规则 3、组件创建规则 3.1、首次渲染 3.2、非首次渲染 4、使用场景 4.1、数据源不变 4.2、数据源组项发生变化 4.3、数据源数组项子属性变化 5、反例 5.1、渲染结果非预期 5.2、渲染性能降低 Android开发中我们有ListView组件、GridVi…

大创项目推荐 深度学习+opencv+python实现车道线检测 - 自动驾驶

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络3.1卷积层3.2 池化层3.3 激活函数:3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 YOLOV56 数据集处理7 模型训练8 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 &am…

【Element】el-table 使用 el-table-infinite-scroll 插件实现滚动加载

虽然 el 官方提供了 Infinite Scroll 无限滚动 组件 但是却不支持 el-table 组件,这就很难受了,还好已经有大佬写好了插件,并且支持 element-plus/infinite-scroll 组件的所有选项。 el-table-infinite-scroll el-table-infinite-scroll 看…

计算机操作系统学习笔记

一、什么是操作系统 1、概念 操作系统(operating system,简称OS)是管理计算机硬件与软件资源的计算机程序。操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入设备与输出设备、操作网络与管理文件系统等基本事务。 …

第十二章 异常-Exception

一、异常的概念(P444) Java 语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常) 执行过程中所发生的异常事件可分为两大类 (1)Error(错误…

Midjourney v6 正式发布,AI创新工坊同步更新

Midjourney v6 开发团队将从2023 年 12 月 21 日今晚开始,在寒假期间让社区测试Midjourney v6模型的 alpha 版本。 要打开它,V6请从提示下方的下拉菜单中选择/settings或--v 6在提示后键入。 Midjourney v6 基本型号有哪些新功能? 更准确的…

python3 数据分析项目案例,用python做数据分析案例

本篇文章给大家谈谈python3 数据分析项目案例,以及用python做数据分析案例,希望对各位有所帮助,不要忘了收藏本站喔。 目录 一丶可视化绘图案例 1.曲线图 2.柱形图 3.点线图 4.3D散点图 5. 绘制漏斗图 6. 绘制词云图 二丶包/模块使用示例 (1)…

IntelliJ IDEA Community(社区版)下载及安装自用版

IntelliJ IDEA Community(社区版)下载及安装自用版 估计是个开发都逃脱不了用IDEA的命运吧,这么好的软件,白嫖了好多年。感恩。 现在很多公司已经不让用商业版的破解版了,所以这里讲的是社区版。 区别: 商…

【音视频】Mesh、Mcu、SFU三种框架的总结

目录 三种网络场景介绍 【Mesh】 【MCU】(MultiPoint Control Unit) 【SFU】(Selective Forwarding Unit) 三种网络架构的优缺点 Mesh架构 MCU架构(MultiPoint Control Unit) SFU架构(Selective Forwarding Unit) 总结 参考文章 三种网络场景介绍 【Mesh】 Mesh架构…

[node]Node.js 模块系统

[node]模块系统 Node.js中的模块系统模块的使用模块的导入模块的导出导出多个值导出默认值导出可传参的函数 文件查找策略从文件模块缓存中加载从原生模块加载从文件加载 Node.js中的模块系统 为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统。 …

如何选择出最适合的backbone模型?图像分类模型性能大摸底

到2023年图像分类backbone模型已经拓展到了几十个系列,而有的新算法还在采样vgg、resnet做backbone,比如2022年提出的GDIP-YOLO还在用VGG16做IA参数预测,那是在浪费计算资源并限制了模型性能的提升,应该将目光放到现在的最新模型中…

【ARM Cortex-M 系列 5 -- RT-Thread renesas/ra4m2-eco 移植编译篇】

文章目录 RT-Thread 移植编译篇编译os.environ 使用示例os.putenv使用示例python from 后指定路径 编译问题_POSIX_C_SOURCE 介绍编译结果 RT-Thread 移植编译篇 本文以瑞萨的ra4m2-eco 为例介绍如何下载rt-thread 及编译的设置。 RT-Thread 代码下载: git clone …

GIT具体配置步骤详解

GIT配置具体步骤如下 SDK 使用 Repo 工具管理,拉取 SDK 需要配置安装 Repo 工具。 Repo is a tool built on top of Git. Repo helps manage many Git repositories, does the uploads to revision control systems, and automates parts of the development workf…

Java小案例-Sentinel的实现原理

前言 Sentinel是阿里开源的一款面向分布式、多语言异构化服务架构的流量治理组件。 主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。 核心概念 要想理解一个新的技…

操作符详解2

一、操作符的属性:优先级、结合性 C语言的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序。 1.优先级 优先级指的是,如果一个表达式包含多个运算符,哪个运算符应该优先执行。各种运算…

C++:第九讲前缀和与差分

Everyday English Your optimal career is simply this: Share the real you with physical world through th e process of creative self-expression. 你的最佳职业很简单,就是这样:通过创造性自我表达的途径和世界分享真实的你。 前言 这节课带你们…

Redis连接不上:主机无法连接虚拟机中的redis服务

1、报错信息 2023-12-22 16:01:25 : Connection: redis-dev > connection failed 2023-12-22 16:01:25 : Click on tree item: 0 2023-12-22 16:01:25 : Connection: Connection error: Connection refused 2、解决 主机<本地>无法连接虚拟机中的redis服务 首先…

智能优化算法应用:基于人工大猩猩部队算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于人工大猩猩部队算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于人工大猩猩部队算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.人工大猩猩部队算法4.实验参…

基于STM32的HC-SR501红外感应模块驱动与应用

一、 简介 HC-SR501红外感应模块是一种常用的人体红外感应模块&#xff0c;常用于安防监控、智能家居等领域。本文将介绍如何在STM32单片机上驱动和应用HC-SR501红外感应模块&#xff0c;实现基本的人体检测功能。 二、 模块原理 HC-SR501红外感应模块基于红外热释电传感器&am…

低代码如何助力企业数字化转型?

目录 一、低代码开发是什么&#xff1f; 二、低代码与企业数字化转型 1&#xff09;集成化 2&#xff09;智能化 3&#xff09;定制化 三、低代码开发平台对于企业数字化转型的优势 01、提供源码 02、私有化部署 03、敏捷开发 04、拓展能力 四、低代码带来的效益 以…