深入解读Redis之数据类型解析-DICT

本文深入剖析Redis中Dict数据类型的实现,包括基础结构、宏定义函数、哈希函数等。Dict作为Redis的核心数据结构,基于哈希表实现,采用开放寻址法解决冲突,通过渐进式rehash来处理哈希表的扩展和收缩。文章详细介绍了字典的各个组成部分和操作,如键值对的添加、查找和删除,以及字典大小的动态调整策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Redis源码之数据类型解析-DICT

当前分析Redis版本为6.2,需要注意。

DICT,字典数据结构,本质是哈希表。相关文件主要有 dict.hdict.c

基础结构

dictEntry 是哈希节点,键值对,其中值采用了联合体 v,从结构体中的 *next 可以看出,这是个单向链表,必然头节点是单独保存的,挂在哈希表上。

typedef struct dictEntry {
   
    void *key; // 键
    union {
   
        void *val;
        uint64_t u64; // unsigned
        int64_t s64; // signed
        double d;
    } v; // 值
    struct dictEntry *next; // 下一个节点地址
} dictEntry;

接着就是 dictType 字典类型,对字典进行了区别,不同类型的字典相关处理函数可以不同。

typedef struct dictType {
   
    // 哈希函数,根据 Key 生成对应的哈希值
    uint64_t (*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); // 销毁值函数
    int (*expandAllowed)(size_t moreMem, double usedRatio); // 扩容函数?
} dictType;

dictht 哈希表结构体。

typedef struct dictht {
   
    dictEntry **table; // 二级指针,存放哈希节点链表的头指针,主要是为了解决哈希冲突
    unsigned long size; // 哈希表大小
    unsigned long sizemask; // 哈希表大小掩码 size-1。计算索引 idx = hash & sizemask
    unsigned long used; // 已有哈希节点数量
} dictht; // 哈希表

dict 字典。

typedef struct dict {
   
    dictType *type; // 字典类型
    void *privdata; // 私有数据
    // 哈希表数组,只有两个哈希表,一般情况,只使用第一个哈希表。
    // ht[1]只会在ht[0]的rehash时使用
    dictht ht[2]; 
    // rehash-idx标识 没有rehash时为-1 rehash 过程索引
    long rehashidx;
    // rehash 暂停标识
    // > 0 表示暂停 < 0 表示编码错误
    int16_t pauserehash;
} dict;

dictIterator 字典迭代器。

typedef struct dictIterator {
   
    dict *d; // 当前迭代字典
    long index; // 迭代器当前所指向的哈希表索引位置
    // table 当前被迭代的哈希表 0|1
    // safe 标识当前迭代器是否安全
    // safe为1是安全的,在迭代过程中,可以执行 dictAdd、dictFind和其他函数,对字典进行修改
    // safe不为1,只能调用dictNext对字典进行迭代。限制了修改
    int table, safe;
    // entry 当前迭代节点指针
    // nextEntry 当前迭代节点的下一个节点,在安全迭代器运行时,entry节点可能会被修改
    // 需要额外指针保存下一节点,防止指针丢失
    dictEntry *entry, *nextEntry;
    // 用于误用检测的不安全迭代器签名
    long long fingerprint;
} dictIterator;

宏定义函数

DICT_NOTUSED

未使用的参数会生成警告?

dictFreeVal

释放指定字典的指定哈希节点值,在定义了值析构函数的情况下,如果没有定义的话,将不会处理。

#define dictFreeVal(d, entry) \
    if ((d)->type->valDestructor) \
        (d)->type->valDestructor((d)->privdata, (entry)->v.val)
dictSetVal

设置指定字典的指点哈希节点值,采用 do/while 结构,保证代码执行,一次。在定义了 valDup 的情况下,使用返回值。反之,直接设置。

#define dictSetVal(d, entry, _val_) do {
      \
    if ((d)->type->valDup) \
        (entry)->v.val = (d)->type->valDup((d)->privdata, _val_); \
    else \
        (entry)->v.val = (_val_); \
} while(0)
dictSetSignedIntegerVal

设置哈希节点值,有符号整型。直接对 entry->v.s64 进行赋值。

dictSetUnsignedIntegerVal

设置哈希节点值,无符号整型。直接对 entry->v.u64 进行赋值。

dictSetDoubleVal

设置哈希节点值,双精度型。直接对 entry->v.d 进行赋值。

dictFreeKey

释放指定字典的指定哈希节点键,同 dictFreeVal 相似。

dictSetKey

设置指定字典的指定哈希节点键,同 dictSetVal 相似。

dictCompareKeys

比较指定字典的两个哈希键。如果存在 d->type->keyCompare,就调用 keyCompare 进行比较,否则直接 key1 == key2 比较。

dictHashKey

生成指定字典指定键的哈希值。

dictGetKey

获取指定哈希节点键。

dictGetVal

获取指定哈希节点值。

dictGetSignedIntegerVal

获取指定哈希节点的有符号整型值。

dictGetUnsignedIntegerVal

获取指定哈希节点的无符号整型值。

dictGetDoubleVal

获取指定哈希节点的双精度值。

dictSlots

获取指定字典的插槽总量 ht[0].size + ht[1].size

dictSize

获取指定字典的哈希表已使用节点总量。

dictIsRehashing

判断指定字典是否正在 rehash。

dictPauseRehashing

暂停指定字典 rehash 过程。

dictResumeRehashing

继续指定字典 rehash 过程。

常量和变量

DICT_OK

宏定义常量。表示字典处理成功。

DICT_ERR

宏定义常量。表示字典处理失败。

DICT_HT_INITIAL_SIZE

宏定义常量。哈希表初始化大小。

dictTypeHeapStringCopyKey

外部变量。字典类型,堆字符串拷贝键?

dictTypeHeapStrings

外部变量。字典类型,堆字符串?

dictTypeHeapStringCopyKeyValue

外部变量。字典类型,堆字符串拷贝键值?

dict_can_resize

静态变量。哈希表大小是否可调整。但该值为0,也不是所有调整都被阻止。

dict_force_resize_ratio

静态变量。哈希表大小强制调整比例。 used/size,如果该比例大于 dict_force_resize_ratio,就需要强制调整。

dict_hash_function_seed[16]

静态变量。哈希函数种子。

哈希函数

dictSetHashFunctionSeed

设置哈希种子。使用 memcpy 实现。

dictGetHashFunctionSeed

获取哈希种子。直接返回 dict_hash_function_seed 即可。

dictGenHashFunction

哈希函数。套壳 siphash 实现,具体实现在 siphash.c 文件。

dictGenCaseHashFunction

也是哈希函数。套壳 siphash_nocase 实现,具体实现在 siphash.c 文件。

私有函数

_dictExpandIfNeeded

如果需要,扩展哈希表。调用 dictExpand 接口函数实现。

static int _dictExpandIfNeeded(dict *d)
{
   
    // 正在 rehash 过程,不支持扩展,直接返回即可
    if (dictIsRehashing(d)) return DICT_OK;

    // 如果空哈希表,直接扩展到初始大小 DICT_HT_INITIAL_SIZE
    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);

    // 条件一 哈希表已使用大小 >= 哈希表大小,说明已经饱和或溢出
    // 条件二 dict_can_resize 是否支持大小调整 或 已达到强制扩展比例(负载因子)
    // 条件三 该字典类型允许扩展调整
    // 扩展到 used+1 大小。。。这样该函数触发可能比较频繁。只增已有节点+1。
    if (d->ht[0].used >= d->ht[0].size &&
        (dict_can_resize ||
         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio) &&
        dictTypeExpandAllowed(d))
    {
   
        return dictExpand(d, d->ht[0].used + 1);
    }
    return DICT_OK;
}
_dictNextPower

将初始哈希表大小做幂级扩展,直到大于等于给定 size

static unsigned long _dictNextPower(unsigned long size)
{
   
    unsigned long i = DICT_HT_INITIAL_SIZE; // 初始大小 4

    if (size >= LONG_MAX) return LONG_MAX + 1LU; // 不会溢出?
    while(1) {
   
        if (i >= size) // i = 2^n
            return i;
        i *= 2;
    }
}
dictTypeExpandAllowed

判断指定字典是否允许哈希表扩展。主要根据字典类型中的 expandAllowed 函数。

static int dictTypeExpandAllowed(dict *d) {
   
    if (d->type->expandAllowed == NULL) return 1;// 没设置,默认可以?
    return d->type->expandAllowed(
                    _dictNextPower(d->ht[0].used + 1) * sizeof(dictEntry*),
                    (double)d->ht[0].used / d->ht[0].size);
}
_dictKeyIndex

返回可用插槽的索引(头节点)。如果在 rehash 过程,总是返回 ht[2] 中的索引。

static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
{
    // hash 由 key 生成。那这里为什么不调用哈希函数生成,反而传参?
    unsigned long idx, table;
    dictEntry *he; // 哈希节点
    if (existing) *existing = NULL; // 已存在哈希节点。强行重置。
    if (_dictExpandIfNeeded(d) == DICT_ERR) return -1; // 扩展哈希表如果有需要
    
    for (table = 0; table <= 1; table++) {
    // 在ht[0]和ht[1]中检索,可能在rehash过程。需要循环
        idx = hash & d->ht[table].sizemask; // 索引 哈希值和哈希表大小掩码
        he = d->ht[table].table[idx]; // 获取相应哈希头节点,开启循环检索 key 毕竟单向链表
        while(he) {
   
            if (key==he->key || dictCompareKeys(d, key, he->key)) {
    // 存在 返回 -1
                if (existing) *existing = he;
                return -1;
            }
            he = he->next;
        }
        if (!dictIsRehashing
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值