redis 基础数据结构之 hash表

本文深入探讨了Redis中的哈希表数据结构,从字典结构、哈希表的实现细节到键值对的添加、哈希表的扩容与缩容策略,详细解析了Redis如何高效管理哈希数据类型。通过对hset命令的操作分析,展示了从ziplist转换到哈希表的过程,以及在哈希表退化成链表时的解决方案。
摘要由CSDN通过智能技术生成

给新观众老爷的开场

大家好,我是弟弟!
最近读了一遍 黄健宏大佬的 <<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的默认设置,将被转换为 哈希表实现。
转换过程为

  1. 创建一个哈希表实现的 hash对象
  2. 使用哈希对象迭代器遍历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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值