redis键值对映射关系存储-Dict

news2024/11/25 18:58:19

基本概述

Redis是一个键值型(Key-Value Pair)的数据库,可以根据键实现快速的增删改查。而键与值的映射关系正是通过Dict来实现的

Dict由三部分组成,分别是:哈希表(DictHashTable)哈希节点(DictEntry)字典(Dict)

哈希表:

image-20230613214446162

哈希节点:

image-20230613214626071

size大小只能是 2^n

sizemark一定要是 2^n - 1,才会有如下效果

与sizemark与运算实际上与 size求余效果一样(hash运算)

向Dict添加键值对时,Redis首先根据key计算出hash值(h),然后利用 h & sizemask来计算元素应该存储到数组中的哪个索引位置

例如:存储k1=v1,假设k1的哈希值h = 1,则1 & 3 = 1,因此k1 = v1要存储到数组角标1位置。

image-20230613215003753

(size默认大小是4)

假设k2哈希值也是1,相同hash值节点,拉链法(加在队首)

image-20230613215403279

字典:

image-20230613215647926

image-20230613215828465

Dict的扩容

Dict中的HashTable就是数组结合单向链表的实现,当集合中元素较多时,必然导致哈希冲突增多,链表过长,则查询效率会大大降低。

Dict在每次新增键值对时都会检查负载因子(LoadFactor = used/size) ,满足以下两种情况时会触发哈希表扩容

  • 哈希表的 LoadFactor >= 1,并且服务器没有执行 BGSAVE 或者 BGREWRITEAOF 等后台进程(消耗CPU,负载因子不是很大,可以忍忍);
  • 哈希表的 LoadFactor > 5(负载因此过大,忍无可忍);

image-20230613220428152

Dict的收缩

Dict除了扩容以外,每次删除元素时,也会对负载因子做检查,当LoadFactor < 0.1 时,会做哈希表收缩:

image-20230613221027506

image-20230613221133386

image-20230613221331037

Dict的rehash

不管是扩容还是收缩,必定会创建新的哈希表,导致哈希表的size和sizemask变化,而key的查询与sizemask有关。因此必须对哈希表中的每一个key重新计算索引,插入新的哈希表,这个过程称为rehash

过程是这样的:

① 计算新hash表的realeSize,值取决于当前要做的是扩容还是收缩:

  • 如果是扩容,则新size为第一个大于等于dict.ht[0].used + 1的2^n
  • 如果是收缩,则新size为第一个大于等于dict.ht[0].used的2^n (不得小于4)

② 按照新的realeSize申请内存空间,创建dictht,并赋值给dict.ht[1]

③ 设置dict.rehashidx = 0,标示开始rehash

④ 将dict.ht[0]中的每一个dictEntry都rehash到dict.ht[1]

⑤ 将dict.ht[1]赋值给dict.ht[0],给dict.ht[1]初始化为空哈希表,释放原来的dict.ht[0]的内存

无论是扩容还是收缩,都会调用dictExpand(),最终调用_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;

    // 如果正在rehash,或者dict节点个数大于扩展数量,直接返回错误
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;

    // 创建一个新的哈希表
    dictht n; /* the new hash table */
    unsigned long realsize = _dictNextPower(size); // 计算出满足条件的 2^n 扩展个数

    // 健壮性判断
    if (realsize < size || realsize * sizeof(dictEntry*) < realsize)
        return DICT_ERR;

    if (realsize == d->ht[0].size) return DICT_ERR;

    // 新哈希表赋值
    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;

    /* Is this the first initialization? If so it's not really a rehashing
     * we just set the first hash table so that it can accept keys. */
    // 第一次初始化,无需rehash,直接初始化第一个哈希表,直接结束
    if (d->ht[0].table == NULL) {
        d->ht[0] = n;
        return DICT_OK;
    }

    /* Prepare a second hash table for incremental rehashing */
    // 不是第一次初始化,准备第二个rehash所需的哈希表
    d->ht[1] = n;
    // 置为0,标识开始rehash
    d->rehashidx = 0;
    return DICT_OK;
}

实际上,redis的rehash流程不是逐个节点都rehash到dict.ht[1],假设节点个数成千上万这个过程是比较耗时的,不是特别高效

Dict的渐进式rehash

Dict的rehash并不是一次性完成的。试想,如果Dict中包含数百万的entry,要在一次rehash完成,极有可能导致主线程阻塞。因此Dict的rehash是分多次、渐进式的完成,因此称为渐进式rehash

实际完整流程如下:

① 计算新hash表的size,值取决于当前要做的是扩容还是收缩:

  • 如果是扩容,则新size为第一个大于等于dict.ht[0].used + 1的2^n
  • 如果是收缩,则新size为第一个大于等于dict.ht[0].used的2^n (不得小于4)

② 按照新的size申请内存空间,创建dictht,并赋值给dict.ht[1]

③ 设置dict.rehashidx = 0,标示开始rehash

将dict.ht[0]中的每一个dictEntry都rehash到dict.ht[1]

每次执行新增、查询、修改、删除操作时,都检查一下dict.rehashidx是否大于-1,如果是则将dict.ht[0].table[rehashidx]的entry链表rehash到dict.ht[1],并且将rehashidx++。直至dict.ht[0]的所有数据都rehash到dict.ht[1]

⑤ 将dict.ht[1]赋值给dict.ht[0],给dict.ht[1]初始化为空哈希表,释放原来的dict.ht[0]的内存【每次操作(增删改查),rehash只操作数组一个角标上的元素,直至所有元素迁移完成,重置两个dictht

⑥ 将rehashidx赋值为-1,代表rehash结束

在rehash过程中,新增操作,则直接写入ht[1],查询、修改和删除则会在dict.ht[0]和dict.ht[1]依次查找并执行。这样可以确保ht[0]的数据只减不增,随着rehash最终为空

小结

Dict的结构:

  • 类似java的HashTable,底层是数组加链表来解决哈希冲突
  • Dict包含两个哈希表,ht[0]平常用,ht[1]用来rehash

Dict的伸缩:

  • LoadFactor大于5或者LoadFactor大于1并且没有子进程任务时,Dict扩容
  • LoadFactor小于0.1并且哈希表大小大于初始值4时,Dict收缩
  • 扩容大小为第一个大于等于used + 1的2^n
  • 收缩大小为第一个大于等于used 的2^n
  • Dict采用渐进式rehash每次访问Dict时执行一次rehash,直至所有元素rehash完毕
  • rehash时ht[0]只减不增,新增操作只在ht[1]执行,其它操作在两个哈希表

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

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

相关文章

基于51单片机设计的电动车控制器

一、项目介绍 随着社会经济的快速发展,人们对节能环保的要求越来越高,电动车因其无污染、噪音小、使用成本低等优点逐渐成为了市场关注的焦点。同时,随着科技的不断进步和应用,电动车的技术水平也在不断提高。 为了更好地满足市场需求和科技进步的要求,本项目基于51单片…

车载以太网 - 数据链路层 - VLAN

数据链路层通信 以太网二层数据链路层的寻址方式、帧结构、及 VLAN (Virtual LocalArea Network)&#xff0c;其分为LLC(Logical Link Control)逻辑链路控制子层&#xff0c;和 MAC(Media Access Control)媒体访问控制子层&#xff0c;其中&#xff0c;MAC 子层负责以太网的总…

chatgpt赋能python:Python中的异常处理

Python中的异常处理 在Python编程中&#xff0c;异常是指程序出现了不正常的情况&#xff0c;比如语法错误、运行时错误等等。这些异常会导致程序崩溃&#xff0c;所以我们需要在程序中使用异常处理来避免这种情况的发生。 什么是异常处理&#xff1f; 异常处理是一种技术&a…

chatgpt赋能python:Python如何把多行合并成一行

Python如何把多行合并成一行 如果你常常需要处理文本数据&#xff0c;你就会遇到将多行文本合并成一行的需求。在Python中&#xff0c;这个任务非常简单&#xff0c;本文将介绍如何使用Python实现把多行合并成一行。 什么是多行文本&#xff1f; 在编程和文本处理中&#xf…

【MySQL高级篇笔记-其他数据库日志(下) 】

此笔记为尚硅谷MySQL高级篇部分内容 目录 一、MySQL支持的日志 1、日志类型 2、日志的弊端 二、慢查询日志(slow query log) 三、通用查询日志(general query log) 1、问题场景 2、查看当前状态 3、启动日志 方式 1 &#xff1a;永久性方式 方式2&#xff1a;临时性…

回归预测 | MATLAB实现基于LSTM-AdaBoost长短期记忆网络结合AdaBoost多输入单输出回归预测

回归预测 | MATLAB实现基于LSTM-AdaBoost长短期记忆网络结合AdaBoost多输入单输出回归预测 目录 回归预测 | MATLAB实现基于LSTM-AdaBoost长短期记忆网络结合AdaBoost多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现基于LSTM-Ad…

chatgpt赋能python:Python如何将多个图像合并输出?

Python如何将多个图像合并输出&#xff1f; 如果您需要将多个图像合并为一个&#xff0c;并将其输出到一个文件中&#xff0c;则Python提供了一些简单的解决方案。在本文中&#xff0c;我们将介绍如何使用Python进行该操作&#xff0c;并附上示例和用法说明。 PIL库 Python …

计算机图形学与opengl C++版 学习笔记 第11章 参数曲面

目录 11.1 二次贝塞尔曲线(三点)11.2 三次贝塞尔曲线(四点)11.3 二次贝塞尔曲面(3x3控制点)11.4 三次贝塞尔曲面(4x4控制点)补充说明 在20世纪50年代和60年代在雷诺公司工作期间&#xff0c;皮埃尔贝塞尔&#xff08;Pierre Bzier&#xff09;开发了用于设计汽车车身的软件系统…

【信息与内容安全复习】第二章知识要点总结

1.网络媒体信息的获取流程 2.三原色原理 3.颜色特征表达的特点、问题和主要方法 4.文本特征表达的方式 5.网络媒体信息与网络通讯信息 6.网络媒体信息获取方法 7.补充 8.视觉特征表达的应用 9.颜色特征表达举例之颜色直方图 10.纹理特征和局部特征 答&#xff1a; 1.网络媒体…

Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解

文章目录 0.前言1. 字典的结构2. 源码解析2.1. 字典的结构体2.2. 字典的函数接口dictAdddictFinddictResize 3. 字典/哈希表的优缺点3.1 优点3.1.1. 快速的查找时间3.1.2. 动态调整大小3.1.3. 灵活的数据类型3.2 缺点 4.总结5. Redis从入门到精通系列文章 0.前言 上个篇章回顾…

java三大特性之【多态】

多态 1.1 概念1.2 实现条件1.3 方法重写&#xff08;override&#xff09;与方法重载&#xff08;overload&#xff09;1.4 向上转型1.5 向下转型 1.1 概念 同样的一个方法/行为&#xff0c;经过不同的对象&#xff0c;表现出不同的行为&#xff0c;这样的现象就称为多态。 举…

今天就详细告诉你发票识别软件能识别哪些内容

既然大家点进这篇文章&#xff0c;说明大家对增值税发票识别技术非常感兴趣。本文会先介绍增值税发票识别技术的相关知识&#xff0c;然后再具体介绍识别增值税发票的软件有哪些。 增值税发票识别技术是一种基于图像识别和深度学习算法的自动化技术&#xff0c;它可以快速准确…

AI问答:前端需要掌握的设计模式/vue项目使用了哪些设计模式/vue项目开发可以使用哪些设计模式

一、理解什么是设计模式 设计模式是对软件设计开发过程中反复出现的某类问题的通用解决方案。 设计模式是一个在软件设计领域中被广泛应用的概念&#xff0c;它指的是一套被公认为有效的解决特定问题的设计思路和方法。 设计模式更多的是指导思想和方法论&#xff0c;而不是现…

零基础学 MySQL(基础版)

零基础学 MySQL(基础版) 1. 引出 思考一个问题&#xff1a; 淘宝网&#xff0c;京东、微信&#xff0c;抖音 都有各自的功能,那么当我们退出系统的时候&#xff0c;下次再访问时&#xff0c;为什么信息还存在? 2. 解决之道 2.1 解决之道—文件、数据库 为了解决上述问题&…

chatgpt赋能python:Introduction

Introduction 在机器学习中&#xff0c;模型的训练是非常重要的步骤之一。模型训练意味着为数据拟合合适的参数&#xff0c;以便能够准确地预测未来的值。Python是一种功能强大的编程语言&#xff0c;提供许多库和框架来训练机器学习模型。在本文中&#xff0c;我们将探讨如何…

FFmpeg编程入门

标题 播放器框架常用音视频术语解复用器和编解码器FFmpeg库简介FFmpeg有8个常用库&#xff1a; FFmpeg函数简介封装格式相关编解码器相关 FFmpeg数据结构简介关系数据结构分析 播放器框架 流程&#xff1a;媒体文件通过复用器将音频流&#xff0c;视频流&#xff0c;字幕流分离…

iCache dCache

前言 CPU 和 RAM 之间存在多级高速缓存&#xff0c;一般分为 3 级&#xff0c;分别是 L1、L2、L3。 另外&#xff0c;我们的代码都是由两部分组成的&#xff1a;指令、数据。 L1 Cache 比较特殊&#xff0c;每个 CPU 会有两个 L1 Cache&#xff0c;分别为 iCache&#xff08;指…

互联网 Java 工程师高级面试八股文汇总(1260 道题目附解析)

今年的行情&#xff0c;让招聘面试变得雪上加霜。已经有不少大厂&#xff0c;如腾讯、字节跳动的招聘名额明显减少&#xff0c;面试门槛却一再拔高&#xff0c;如果不用心准备&#xff0c;很可能就被面试官怼得哑口无言&#xff0c;甚至失去了难得的机会。 现如今&#xff0c;…

苹果将在 iOS 17 引入新功能,Safari隐私浏览有重大更新

苹果公司正在对Safari隐私浏览系统进行重大更新&#xff0c;为用户在浏览网页时提供更好的保护&#xff0c;防止第三方跟踪器。 iPhone制造商说&#xff1a;先进的跟踪和指纹保护有助于防止网站使用最新的技术来跟踪或识别用户的设备。隐私浏览现在可以在不使用时锁定&#xf…

MODIS数据下载

MODIS数据常用下载网址&#xff1a; Find Data - LAADS DAAC 在下载之前需要注册一个账号&#xff0c;才可进行下载。 1.选择数据产品&#xff0c;本人选取MOD09Q1数据产品&#xff08;250m8天合成的反射率数据&#xff09; 2.设置时间限制如下 3.找到感研究区域所在的位置&…