redis底层数据结构之字典(dict)
字典(dict)
字典又称为符号表或者关联数组、或映射(map),是一种用于保存键值对(key-value)的抽象数据结构
字典中的每个key都是唯一的,通过key对值来进行查找或修改,时间复杂度为 O(1)
redis的底层数据结构字典又使用了哈希表,一个哈希表包含多个哈希表节点,每个哈希表节点保存了字典中的一个键值对
1 字典结构
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
int rehashidx;
} dict;
其中:
type:类型特定函数,保存了一系列操作dict的函数, redis会为用途不同的字典设置不同的类型特定函数
privdata:私有数据,需要传给type属性中定义的类型特定函数的可选参数
dictht:哈希表,包含两个元素,每个元素都是一个哈希表,一般情况下,字典只使用 ht[0]哈希表,ht[1]哈希表只会在对 ht[0] 哈希表进行 rehash 时使用
rehashidx:rehash索引,不在进行rehash时,值为 -1
2 dictType(类型特定函数)结构
typedef struct dictType {
unsigned int (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key);
void *(*valDup)(void *privdata, const void *obj);
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
} dictType;
其中:
hashFunction:计算哈希值的函数
keyDup:复制键的函数
valDup:复制值的函数
keyCompare:对比键的函数
keyDestructor:销毁键的函数
valDestructor:销毁值的函数
3 dictht(哈希表)结构
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
其中:
table:哈希表数组,每一个哈希表数组元素都保存了一对键值对
size:哈希表的大小
sizemask:哈希表掩码,值等于 size-1,用于通过hash算法计算索引值
used:哈希表已使用节点的数量
4 dictEntry(哈希表节点)结构
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
} v;
struct dictEntry *next;
} dictEntry;
其中:
key:dict中的key
v:dict的value,可以是一个指针,也可以是uint64_t 整数,也可以是int64_t整数
next:指向下一个哈希表节点的指针,当有hash冲突时,解决hash冲突使用,redis使用链地址法解决哈希冲突
5 字典示意图
存有4个key-value对且没有进行rehash的字典结构如下:
type=REDIS_SET 或 REDIS_HASH

6 hash算法
dict要添加新的key-value对时, redis使用hash算法计算索引值, 根据索引值将key-value对放到哈希表数组的对应位置
redis中计算索引值步骤如下:
1) 计算key的哈希值
使用dict结构的type属性中定义的类型特定函数,计算键(key)的哈希值
redis 使用 MurmurHash2 算法来计算键的哈希值
hash = dict->type->hashFunction(key);
2) 计算索引值
使用哈希表的sizemake属性和哈希值,计算出索引值,根据情况不同:ht[x]可以是 ht[0]或者 ht[1]
index = hash & dict->ht[x].sizemask;
7 hash冲突
哈希表会存在哈希冲突,解决哈希冲突的方法有:开放地址法和链地址法
redis采用的是链地址法,通过next指针可以将多个哈希值相同的键值对连接在一起,用来解决哈希冲突
rehash会增加现有的哈希桶数量,让逐渐增大的entry元素能在更多的桶之间分散保存,减少单个桶中的元素数量,从而减少单个桶中的冲突
8 扩容和收缩(rehash)
当哈希表保存的key-value对太多或太少时,就要通过rehash(重新散列)对哈希表进行扩展或者收缩
dict中存有的两张哈希表:
ht[0]哈希表表示字典正在使用的哈希表,key-value对存在ht[0]中
ht[1]哈希表表示rehash时使用的哈希表,没有申请结点内存空间,只有表结构,不会占用很大的内存空间
步骤如下:
1) 为ht[1]重新分配空间,分配的空间大小取决于要执行的操作,以及ht[0]当前包含的key-value对数量(即ht[0].used的值)
a 如果执行的是扩展操作,扩展为原哈希表已使用的空间的2倍
b 如果执行的是收缩操作,收缩为原哈希表已使用空间的一半
2) 使用hash算法重新计算键的哈希值和索引,然后将key-value对存放到ht[1]的对应位置上
3) 当ht[0]包含的所有key-value对都迁移到ht[1]之后(ht[0]变为空),释放ht[0],将ht[1]设置为ht[0],rehashidx设置为 -1
9 渐近式rehash
扩容和收缩操作不是一次性、集中式完成的,而是分多次、渐进式完成的
如果哈希表里保存的key-vlaue对的数量在百万级别,那么一次性将所有key-vlaue对rehash,可能会造成redis在一段时间内不能进行别的操作,所以redis采用渐进式rehash,分多次、渐进式完成地将ht[0]里面的key-vlaue对慢慢地rehash到ht[1]
dict中索引计数器变量rehashidx=0表示开始rehash,每次处理请求时将ht[0]索引位置(rehashidx=上一个rehashidx+1)的所有key-value对拷贝到ht[1],完成拷贝时,rehashidx+1;
渐进式rehash期间,字典的删除、查找、更新等可能会在两个哈希表上进行(不是同时进行),第一个哈希表没有找到,就会去第二个哈希表上进行查找;但是插入key-value对一定是在新的哈希表上进行
10 扩容和收缩的条件
负载因子 = 哈希表已保存节点数量 / 哈希表大小
1) 扩容操作
a 服务器目前没有在执行bgsave命令或者bgrewriteaof命令并且哈希表的负载因子大于等于1
b 服务器目前正在执行bgsave命令或者bgrewriteaof命令并且哈希表的负载因子大于等于5
2) 收缩操作
a 哈希表的负载因子小于 0.1 ,程序自动开始对哈希表执行收缩操作
redis底层数据结构之字典(dict)的更多相关文章
- Redis 底层数据结构之字典
文章参考 <Redis 设计与实现>黄建宏 字典 在字典中,每个键都是独一无二的,程序可以在字典中根据键查找与之相关联的值,或者通过键来更新和删除值. 字典在 Redis 中的应用相当广泛 ...
- Redis 底层数据结构介绍
Redis 底层数据结构 版本:2.9 支持的数据类型: 字符串 散列 列表 集合 有序集合 字符串 Redis 利用原生的 c 字符串进行了一次封装.封装的字符串叫做简单动态字符串:SDS(simp ...
- Redis底层数据结构详解
上一篇说了Redis有五种数据类型,今天就来聊一下Redis底层的数据结构是什么样的.是这一周看了<redis设计与实现>一书,现来总结一下.(看书总是非常烦躁的!) Redis是由C语言 ...
- 【redis】redis底层数据结构原理--简单动态字符串 链表 字典 跳跃表 整数集合 压缩列表等
redis有五种数据类型string.list.hash.set.zset(字符串.哈希.列表.集合.有序集合)并且自实现了简单动态字符串.双端链表.字典.压缩列表.整数集合.跳跃表等数据结构.red ...
- Redis学习笔记(二)redis 底层数据结构
在上一节提到的图中,我们知道,可以通过 redisObject 对象的 type 和 encoding 属性.可以决定Redis 主要的底层数据结构:SDS.QuickList.ZipList.Has ...
- 探索Redis设计与实现2:Redis内部数据结构详解——dict
本文转自互联网 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial ...
- Redis底层数据结构实现
REDIS 较宽泛的支持5种数据结构 分别为 字符串 列表 集合 散列 有序集合 关于这几种数据结构的使用 相信网上有很多资料,查看官网API 也很详细了 读者可以自己随意翻阅 很方便 . 接下 ...
- Python学习笔记(5)--数据结构之字典dict
字典(dict) 定义:键值对集合 初始化:{}, {'1' : 'abc', '2' : 'def'} 1.增加:单个数据直接赋值 update(dict2) ---把dict2的元素加入到dic ...
- redis底层数据结构--简单动态字符串 链表 字典 跳跃表 整数集合 压缩列表
1.动态字符串 redis中使用c语言的字符床存储字面量,默认字符串存储采用自己构建的简单动态字符串SDS(symple dynamic string) redis包含字符串的键值对都是用SDS实现的 ...
- Redis 的底层数据结构(字典)
字典相对于数组,链表来说,是一种较高层次的数据结构,像我们的汉语字典一样,可以通过拼音或偏旁唯一确定一个汉字,在程序里我们管每一个映射关系叫做一个键值对,很多个键值对放在一起就构成了我们的字典结构. ...
随机推荐
- DVWA靶场实战(七)——SQL Injection
DVWA靶场实战(七) 七.SQL Injection: 1.漏洞原理: SQL Inject中文叫做SQL注入,是发生在web端的安全漏洞,主要是实现非法操作,例如欺骗服务器执行非法查询,他的危害在 ...
- 【Machine Teaching】An Overview of Machine Teaching
Machine Teaching 1 Introduction 1️⃣ 什么是 Machine Teaching? searching the optimal (usually minimal) te ...
- P1605迷宫——题解
展开 题目背景 给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过.给定起点坐标和终点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案.在迷宫中移动有上下左右四种方式,每次 ...
- 近邻取样插值方法缩放BGRA图片数据
近邻取样插值原理: 对于缩放后图片中的某点 (Dx, Dy) 对应于原图片中的点 (Sx, Sy),它们之间存在如下的比例关系: (Sx-0)/(SW-0)=(Dx-0)/(DW-0) (Sy-0)/ ...
- flutter 2.x运行flutter run 报错Cannot run with sound null safety, because the following dependenciesdon'
flutter 2.x运行flutter run 报错Cannot run with sound null safety, because the following dependenciesdon' ...
- 轻松理解Promise.all 、Promise.then、Promise.race有什么区别以及使用方法
简单来说呢,Promse.all一般应用于某个场景需要多个接口数据合并起来才能实现 有个极大地好处我必须说一下,请求顺序和获取数据顺序是一样的哟,大可放心使用~~ const success1 = n ...
- Python从零到壹丨图像增强及运算:图像掩膜直方图和HS直方图
摘要:本章主要讲解图像直方图相关知识点,包括掩膜直方图和HS直方图,并通过直方图判断黑夜与白天,通过案例分享直方图的实际应用. 本文分享自华为云社区<[Python从零到壹] 五十二.图像增强及 ...
- 问题记录:VMware vSphere vCenter 7.0 上传文件失败
问题记录:VMware vSphere vCenter 7.0 上传文件失败 环境说明: VC版本:VMware vSphere vCenter 7.0 ESXi版本:VMware vSphere E ...
- MySQL 合并查询join 查询出的不同列合并到一个表中
为了求解问题时思路清晰,建议先分列查询,再将列合并到一个表中,这样相当于将复杂问题拆解为简单问题,一一解决.优点是避免所有问题混在一起,代码逻辑清晰,可迁移性强,下次遇到类似的查询问题能快速求解,缺点 ...
- Djanngo-bbs项目
1.项目开发基本流程 1.需求分析 2.架构设计 3.分组开发 4.提交测试 5.交付上线 2.项目流程 仿造博客园项目(核心:文章的增删改查) 1.表分析: 1.1用户表 1.2个人站点表 1.3文 ...