redis 基础数据结构之 hash表
给新观众老爷的开场
大家好,我是弟弟!
最近读了一遍 黄健宏大佬的 <<Redis 设计与实现>>,对Redis 3.0版本有了一些认识
该书作者有一版添加了注释的 redis 3.0源码
👉官方redis的github传送门。
👉黄健宏大佬添加了注释的 redis 3.0源码传送门
网上说Redis代码写得很好,为了加深印象和学习redis大佬的代码写作艺术,了解工作中使用的redis 命令背后的源码逻辑,便有了写博客记录学习redis源码过程的想法。
redis 的hash数据类型
redis 的hash数据类型,可以由 ziplist和 hash表 两种数据结构实现。
上篇博客传送门:redis源码阅读 - 基础数据结构 之 ziplist
下面来看下 hash表的数据结构👇
哈希表
redis里的hash表数据结构是使用链表地址法实现的。
字典结构
redis哈希表在内部封装在一个 dict对象里
/*
* 字典
*/
typedef struct dict {
// 类型特定函数
dictType *type;
// 私有数据
void *privdata;
// 哈希表
dictht ht[2];
// rehash 索引
// 当 rehash 不在进行时,值为 -1
int rehashidx; /* rehashing not in progress if rehashidx == -1 */
// 目前正在运行的安全迭代器的数量
int iterators; /* number of iterators currently running */
} dict;
- dictType *type
该字段存放着该字典哈希表上进行特定操作的具体函数
比如通过key计算hash值的hashFunction函数
/*
* 字典类型特定函数
*/
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;
- void *privdata
配合 dictType *type 使用
- dictht ht[2]
该dict对象有两个hash表
其中 key/value 一般都存在 dict->ht[0]里
当满足一定条件时,将对 ditc->ht[0]里的key/value 进行 rehash操作,此时 dict->ht[1] 将被用上。
- int rehashidx
rehash操作的标示字段,表示 ht[0] 上准备进行rehash操作的下标
- int iterators
安全迭代器数量,如果>0,rehash操作将不会进行。
哈希表结构
- dictEntry **table
指向哈希表数组 - unsigned long size
哈希表数组的长度 - unsigned long sizemask
哈希表掩码,用于计算key的hash值对应的哈希表数组下标 - unsigned long used
该哈希表中已有节点数量
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table.
* 哈希表
* 每个字典都使用两个哈希表,从而实现渐进式 rehash 。
*/
typedef struct dictht {
// 哈希表数组
dictEntry **table;
// 哈希表大小
unsigned long size;
// 哈希表大小掩码,用于计算索引值
// 总是等于 size - 1
unsigned long sizemask;
// 该哈希表已有节点的数量
unsigned long used;
} dictht;
哈希表数组中的节点
哈希表数组某一个下标存放的是 *dictEntry 链表的表头。
一个 dictEntry 里保存了一对 key/value、以及下一个dictEntry的指针
typedef struct dictEntry {
// 键
void *key;
// 值
union {
void *val;
uint64_t u64;
int64_t s64;
} v;
// 指向下个哈希表节点,形成链表
struct dictEntry *next;
} dictEntry;
从hset 命令与哈希表相关的部分
通过命令表找到 hset
命令的处理函数 hsetCommand
hsetCommand里 会优先尝试创建 ziplist数据结构实现的hash数据类型,因为ziplist省空间啊,
哈希表中的 field-value 被当成两个ziplist元素,一前一后加入到ziplist尾部
如果ziplist元素个数或者 单个元素的大小超过了ziplist的默认设置,将被转换为 哈希表实现。
转换过程为
- 创建一个哈希表实现的 hash对象
- 使用哈希对象迭代器遍历ziplist将所有key/value 取出后添加到哈希表中,
并将ziplist结构、与迭代器占用的空间释放
(可以在 t_hash.c/hashTypeConvertZiplist 里找到相应的源码逻辑)
创建哈希表方式实现的哈希对象
实际上就是创建了一个字典。
根据传入的dictType *type, void *privDataPtr 两个字段创建并初始化一个字典.
这里传入的dictType *type 是 &hashDictType, privDataPtr 为NULL
/* Hash type hash table (note that small hashes are represented with ziplists) */
dictType hashDictType = {
dictEncObjHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
dictEncObjKeyCompare, /* key compare */
dictRedisObjectDestructor, /* key destructor */
dictRedisObjectDestructor /* val destructor */
};
/* 将一个 ziplist 编码的哈希对象 o 转换成其他编码 */
void hashTypeConvertZiplist(robj *o, int enc) {
...
// 创建空白的新字典
dict = dictCreate(&hashDictType, NULL);
...
}
/* Create a new hash table
* 创建一个新的字典
*/
dict *dictCreate(dictType *type,
void *privDataPtr)
{
dict *d = zmalloc(sizeof(*d));
_dictInit(d,type,privDataPtr);
return d;
}
//* Initialize the hash table */
//* 初始化哈希表
int _dictInit(dict *d, dictType *type,
void *privDataPtr)
{
// 初始化两个哈希表的各项属性值
// 但暂时还不分配内存给哈希表数组
_dictReset(&d->ht[0]);
_dictReset(&d->ht[1]);
// 设置类型特定函数
d->type = type;
// 设置私有数据
d->privdata = privDataPtr;
// 设置哈希表 rehash 状态
d->rehashidx = -1;
// 设置字典的安全迭代器数量
d->iterators = 0;
return DICT_OK;
}
static void _dictReset(dictht *ht)
{
ht