redis字典数据结构解析

本文深入解析Redis中的字典数据结构,包括哈希表节点、字典结构、迭代器实现以及重散列过程。介绍了如何处理键值对的添加、删除、查找和更新,以及在重散列期间如何确保安全和避免重复遍历的问题。
摘要由CSDN通过智能技术生成

数据结构

哈希表节点

// 字典中的键值对
typedef struct dictEntry {
   
    void *key;
    union {
   
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

可以发现, 键值对的值是一个union类型, 既可以表示一个指针, unit64_t, int64_t或者double数据, 都是8字节的数据. 同时存在一个指向哈希表节点的next变量, 以此来解决键的冲突的问题(拉链表解决).

哈希表结构

typedef struct dictht {
   
    dictEntry **table;        // 哈希表数组
    unsigned long size;       // 哈希表数组的大小
    unsigned long sizemask;   // 等于size - 1, 用于计算节点的索引值
    unsigned long used;       // 哈希表包含的节点个数
} dictht;

字典的数据结构

typedef struct dict {
   
    dictType *type; // 类型特定函数
    void *privdata;
    dictht ht[2];   // 保存两个哈希列表, 实现渐进式的重散列, ht[0]表示旧哈希列表, ht[1]表示新扩展或压缩新指定的哈希列表
    long rehashidx; // 冲散列索引, 表示当前已经进行到的索引, -1表示没有进行重散列
    int iterators;  // 正在使用的安全模式下iterator的个数, 只有在iterator=0时, 才会进行rehash 
} dict;

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;

由于使用数组存储哈希节点, 所以会存在数组空间不足, 需要动态扩充, 由于扩充时, 需要遍历所有节点重新计算节点的索引, 如果一次性完成, 需要消耗一定的时间, 回导致服务器在一段时间内不可用.为了避免重散列对服务器的性能造成影响, 分多次将ht[0]里的节点重散列到ht[1]中. 将重散列所需的工作军均摊到对字典的每个添加, 删除, 查找和更新的操作上, 避免了集中式重散列带来的庞大计算量.但是代码的开发难度会明显增大.

同时为了支持创建多态字典, dict数据结构中指定了dictType类型变量*type, 用于操作不同类型的键值对

迭代器的数据结构

typedef struct dictIterator {
   
    dict *d;                // 被迭代的字典
    long index;             // 迭代器当前所指向的哈希表索引位置
    int table;              // 正在被迭代的哈希表, ht[0]和ht[1]
    int safe;               // 标识迭代器是否处于安全模式
    dictEntry *entry;       // 当前迭代的节点指针
    dictEntry *nextEntry;   // 当前迭代节点的下一个节点指针
    long long fingerprint;  // 非安全模式下使用  
} dictIterator;

可以发现, 迭代器的数据结构中含有一个save字段, 就是为了解决渐进式重散列导致的不安全问题而引入的.

采用渐进式重散列, 迭代遍历时可能会存在的问题:

  • 指针entry的失效问题: 因为在遍历列表时, 可能会因为键值过期,而修改列表, 或者插入新的节点, 所以会存在指针安安全问题.
  • 重复遍历问题: 因为遍历整个字典时, 先遍历的哈希列表ht[0], 最后再遍历新的哈希列表ht[1], 如果在遍历ht[1]时, 发生了节点移动, ht[0] —> ht[1], 此时会存在一个节点遍历多次的问题.
安全模式
  1. 遍历过程中可以对字典进行查找和修改,因为查找和修改会触发过期判断,会删除内部元素。
  2. 迭代过程中不会出现重复元素.
非安全模式
  1. 遍历过程中字典是只读的,不能修改字典
  2. 可能会存在重复节点

相关操作

创建新字典

  • dictCreate
static void _dictReset(dictht *ht)
{
   
    ht->table = NULL;
    ht->size = 0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值