《闲扯Redis七》Redis字典结构的底层实现
一、前言
上节《闲扯Redis六》Redis五种数据类型之Hash型 中说到 Hash(哈希对象)的底层实现有:
1、ziplist 编码的哈希对象使用压缩列表作为底层实现
2、hashtable 编码的哈希对象使用字典作为底层实现

那么第二种方式中的字典究竟是怎样的一种结构呢?
字典, 又称符号表(symbol table)、关联数组(associative array)或者映射(map), 是一种用于保存键值对(key-value pair)的抽象数据结构。在字典中, 一个键(key)可以和一个值(value)进行关联(或者说将键映射为值), 这些关联的键和值就被称为键值对。
字典中的每个键都是独一无二的, 程序可以在字典中根据键查找与之关联的值, 或者通过键来更新值, 又或者根据键来删除整个键值对, 等等。
二、实现分析
Redis 的字典采用哈希表作为底层实现, 一个哈希表里面可以有多个哈希表节点, 而每个哈希表节点就保存了字典中的一个键值对。所以咱们依次来分析一下哈希表、哈希表节点、以及字典的结构。
1.哈希表结构
哈希表结构定义 (dict.h/dictht):
typedef struct dictht {
// 哈希表数组
dictEntry **table;
// 哈希表大小
unsigned long size;
// 哈希表大小掩码,用于计算索引值
// 总是等于 size - 1
unsigned long sizemask;
// 该哈希表已有节点的数量
unsigned long used;
} dictht;
描述:
table 属性是一个数组, 数组中的每个元素都是一个指向 dict.h/dictEntry 结构的指针, 每个 dictEntry 结构保存着一个键值对。
size 属性记录了哈希表的大小, 也即是 table 数组的大小, 而 used 属性则记录了哈希表目前已有节点(键值对)的数量。
sizemask 属性的值总是等于 size - 1 , 这个属性和哈希值一起决定一个键应该被放到 table 数组的哪个索引上面。
结构图解:一个空的哈希表
2.哈希表节点
一个哈希表里面可以有多个哈希表节点,那么每个哈希表节点的结构以及多个哈希表节点之间的存储关系是怎么样的呢?
哈希表节点结构定义 (dictEntry):
typedef struct dictEntry {
// 键
void *key;
// 值
union {
void *val;
uint64_t u64;
int64_t s64;
} v;
// 指向下个哈希表节点,形成链表
struct dictEntry *next;
} dictEntry;
描述:
key 属性保存着键值对中的键, 而 v 属性则保存着键值对中的值, 其中键值对的值可以是一个指针, 或者是一个 uint64_t 整数, 又或者是一个 int64_t 整数。
next 属性是指向另一个哈希表节点的指针, 这个指针可以将多个哈希值相同的键值对连接在一次, 以此来解决键冲突(collision)的问题。
结构图解:多个哈希值相同的键值对存储结构,解决键冲突
3.字典结构实现
字典结构定义 (dict.h/dict):
typedef struct dict {
// 类型特定函数
dictType *type;
// 私有数据
void *privdata;
// 哈希表
dictht ht[2];
// rehash 索引
// 当 rehash 不在进行时,值为 -1
int rehashidx; /* rehashing not in progress if rehashidx == -1 */
} dict;
描述:type 属性和 privdata 属性是针对不同类型的键值对, 为创建多态字典而设置的
type 属性是一个指向 dictType 结构的指针, 每个 dictType 结构保存了一簇用于操作特定类型键值对的函数, Redis 会为用途不同的字典设置不同的类型特定函数。
privdata 属性则保存了需要传给那些类型特定函数的可选参数。
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;
ht 属性是一个包含两个项的数组, 数组中的每个项都是一个 dictht 哈希表, 一般情况下, 字典只使用 ht[0] 哈希表, ht[1] 哈希表只会在对 ht[0] 哈希表进行 rehash 时使用。
除了 ht[1] 之外, 另一个和 rehash 有关的属性就是 rehashidx : 它记录了 rehash 目前的进度, 如果目前没有在进行 rehash , 那么它的值为 -1 。
结构图解:普通状态下(没有进行 rehash)的字典
三、哈希表分析
1.哈希算法
当要将一个新的键值对添加到字典里面时, 程序需要先根据键值对的键计算出哈希值和索引值, 然后再根据索引值, 将包含新键值对的哈希表节点放到哈希表数组的指定索引上面。
Redis 计算哈希值和索引值的方法如下:
# 使用字典设置的哈希函数,计算键 key 的哈希值
hash = dict->type->hashFunction(key);
# 使用哈希表的 sizemask 属性和哈希值,计算出索引值
# 根据情况不同, ht[x] 可以是 ht[0] 或者 ht[1]
index = hash & dict->ht[x].sizemask;
如图 4-4:
举个例子, 对于图 4-4 所示的字典来说, 如果我们要将一个键值对 k0 和 v0 添加到字典里面, 那么程序会先使用语句:
hash = dict->type->hashFunction(k0);
计算键 k0 的哈希值。
假设计算得出的哈希值为 8 , 那么程序会继续使用语句:
index = hash & dict->ht[0].sizemask = 8 & 3 = 0;
计算出键 k0 的索引值 0 , 这表示包含键值对 k0 和 v0 的节点应该被放置到哈希表数组的索引 0 位置上,
结构图解:图 4-5
2.键冲突解决
当有两个或以上数量的键被分配到了哈希表数组的同一个索引上面时, 我们称这些键发生了冲突(collision)。
Redis 的哈希表使用链地址法(separate chaining)来解决键冲突: 每个哈希表节点都有一个 next 指针, 多个哈希表节点可以用 next 指针构成一个单向链表, 被分配到同一个索引上的多个节点可以用这个单向链表连接起来, 这就解决了键冲突的问题。
举个例子, 假设程序要将键值对 k2 和 v2 添加到图 4-6 所示的哈希表里面, 并且计算得出 k2 的索引值为 2 , 那么键 k1 和 k2 将产生冲突, 而解决冲突的办法就是使用 next 指针将键 k2 和 k1 所在的节点连接起来。
结构图解:图 4-7

因为 dictEntry 节点组成的链表没有指向链表表尾的指针, 所以为了速度考虑, 程序总是将新节点添加到链表的表头位置(复杂度为 O(1)), 排在其他已有节点的前面。
四、要点总结
1.字典 ht 属性是包含两个哈希表项的数组,一般情况下, 字典只使用 ht[0], ht[1] 哈希表只会在对 ht[0] 哈希表进行 rehash (下节分析) 时使用
2.哈希表使用链地址法(separate chaining)来解决键冲突
3.键值对添加到字典的过程, 先根据键值对的键计算出哈希值和索引值, 然后再根据索引值, 将包含新键值对的哈希表节点放到哈希表数组的指定索引上面

《闲扯Redis七》Redis字典结构的底层实现的更多相关文章
- redis中的字典结构是怎样的?
点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. 基础概念 redis支持的5种数据类型中,有hash类型,hash类型的 ...
- Redis核心原理与实践--散列类型与字典结构实现原理
Redis散列类型可以存储一组无序的键值对,它特别适用于存储一个对象数据. > HSET fruit name apple price 7.6 origin china 3 > HGET ...
- 《闲扯Redis十》Redis 跳跃表的结构实现
一.前言 Redis 提供了5种数据类型:String(字符串).Hash(哈希).List(列表).Set(集合).Zset(有序集合),理解每种数据类型的特点对于redis的开发和运维非常重要. ...
- Redis(一):基本数据类型与底层存储结构
最近在整理有关redis的相关知识,对于redis的基本数据类型以及其底层的存储结构简要的进行汇总和备注(主要为面试用) Redis对外提供的基本数据类型主要为五类,分别是 STRING:可以存储字符 ...
- 【转】Redis学习---哈希结构内存模型剖析
[原文]https://www.toutiao.com/i6594624365906625032/ 概述 在前文<Redis字符串类型内部编码剖析>之中已经剖析过 Redis最基本的 St ...
- Redis实现之字典
字典 字典,又称为符号表(symbol table).关联数组(associative array)或映射(map),是一种用于保存键值对(key-value pair)的抽象数据结构.在字典中,一个 ...
- Redis数据结构之字典
Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对. 一.字典结构定义1. 哈希表节点结构定义: 2. 哈希表结构定义: 3. 字典 ...
- redis系列之------字典
前言 字典, 又称符号表(symbol table).关联数组(associative array)或者映射(map), 是一种用于保存键值对(key-value pair)的抽象数据结构. 在字典中 ...
- Redis(一) 数据结构与底层存储 & 事务 & 持久化 & lua
参考文档:redis持久化:http://blog.csdn.net/freebird_lb/article/details/7778981 https://blog.csdn.net/jy69240 ...
随机推荐
- PHP输入流
PHP输入流php://input,对于这个的官方解释是: php://input可以读取没有处理过的POST数据.相较于$HTTP_RAW_POST_DATA而言,它给内存带来的压力较小,并且不需要 ...
- IDEA创建SpringBoot的多模块项目教程
最近在写一个多模块的SpringBoot项目,基于过程总了一些总结,故把SpringBoot多个模块的项目创建记录下来. 首先,先建立一个父工程: (1)在IDEA工具栏选择File->New- ...
- Spring AOP学习笔记05:AOP失效的罪因
前面的文章中我们介绍了Spring AOP的简单使用,并从源码的角度学习了其底层的实现原理,有了这些基础之后,本文来讨论一下Spring AOP失效的问题,这个问题可能我们在平时工作中或多或少也会碰到 ...
- 看完这篇Redis缓存三大问题,保你面试能造火箭,工作能拧螺丝。
前言 日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题. 一旦涉及大数据量的需求,如一些商品抢购的情景,或者主页访问量瞬间较 ...
- java简介&&变量
Day01 简介 数据 1.数据大体分为两类:基本类型和引用类型 2.基本类型的数据分为四类八种,四类为整型,浮点,布尔,字符 3.Long类型的数据超过int范围要在之后面加个L,不加L是整型会进行 ...
- Flutter —快速开发的IDE快捷方式
老孟导读:这是老孟翻译的精品文章,文章所有权归原作者所有. 欢迎加入老孟Flutter交流群,每周翻译2-3篇付费文章,精彩不容错过. 原文地址:https://medium.com/flutter- ...
- PHP用PHPExcel导出有多个工作表的Excel表格(thinkPHP3.2.3)
直接上代码: //导出excel public function excel() { //引入相关文件 import("Org.Util.PHPExcel"); import(&q ...
- 整理一下CSS最容易躺枪的二十规则,大家能躺中几条?
整理一下CSS最容易躺枪的二十规则,大家能躺中几条? 转载:API中文网 一.float:left/right 或者 position: absolute 后还写上 display:block? 二. ...
- CSS(五)- 背景与边框 - 边框圆角与阴影基础用法
扩展阅读 本文仅仅做border的基础使用,想要深入了解的话可以戳以下几个链接,觉得作者写的很好. CSS Backgrounds and Borders Module Level 3 CSS魔法堂: ...
- 深圳有为JAVA笔试
深圳有为JAVA笔试 1.定义一个线程类有几种方法?分别是什么? 答:两种方法,一种继承Thread类,重写run()方法,第二种实现runnable接口,实现run()方法. 2.抽象类和接口的区别 ...