redis数据结构 - 字典

字典。用于保存键值对,每个键都是唯一的,dict基于哈希表算法,采用某个哈希函数从key计算得到哈希表种的位置。采用拉链法解决冲突,在装载因子(load factor)超过预定值时自动扩展内存,引发重哈希(rehashing)

redis 字典采用增量式重哈希算法(incremental rehashing),在需要扩展内存时避免一次性对所有key进行重哈希,而是将重哈希操作分散到对于dict的各个增删改查的操作中去。每次只对一小部分key进行重哈希,每次重哈希之间不影响dict的操作。从而避免了重哈希期间,单个请求的响应时间剧烈增加。

字典条目dictEntry
typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;


字典方法dictType
typedef struct dictType {
    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);
} dictType;

包含若干函数指针,用于dict调用者自定义对key和value的各种操作,包括:
hashFunction:对key进行哈希值计算的哈希算法
keyDup和valdup:对key、value拷贝,在需要的时候对key和value进行深拷贝,而不仅仅是传递对象指针
keyCompare:比较key
keyDestructor、valDestructor:对key、value析构

字典哈希表dictht
typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

通过指针数组保存哈希桶,记录桶的个数、已使用的个数
table dictEntry指针数组:key的哈希值最终映射到数组的某个位置上(对应一个bucket).如果多个key映射到同一个位置,就发生了冲突,需要拉出一个dictEntry链表
size:dictEntry指针数组的长度,总是2的指数
sizemask:用于将哈希值映射到table的位置索引。等于(size-1)
每个key先经过hashFunction计算得到一个哈希值,然后计算哈希值&sizemask得到在table上的位置(相当于取余)
used:记录dict中现有的数据个数,used/size = 装载因子(load factor),装载因子越大,哈希值冲突概率越高

字典dict
typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    unsigned long iterators; /* number of iterators currently running */
} dict;
 
type 自定义方法:通过自定义的方式使得dict的key和value能够存储任何类型的数据
privdata 私有数据
ht 字典哈希表:只有在重哈希的过程中,ht[0]和ht[1]才都有效。而在平常情况下,只有ht[0]有效,ht[1]里面没有任何数据
rehashidx 重哈希标识:如果rehashidx = -1, 表示当前没有在重哈希过程中;否则,表示当前正在进行重哈希,且它的值记录了当前重哈希进行到哪一步
iterators 当前正在进行遍历的迭代器数量

dict - dictht - dictEntry*[] - dictEntry


创建
static dict *dictCreate(dictType *type, void *privDataPtr) 创建一个dict
static int _dictInit(dict *ht, dictType *type, void *privDataPtr) 初始化dict

查找
static dictEntry *dictFind(dict *ht, const void *key)
如果dict当前正在重哈希,那么_dictRehashStep将重哈希过程推进一步
dictHashKey根据key计算哈希值h, h &= ht sizemask
查找哈希表ht[0],遍历ht[0].table[h] dict entry链表,dictCompareKeys比较key,相等则返回
判断dict当前正在重哈希,那么继续在h[1]上查找,否则直接返回NULL

增量式重哈希
static void _dictRehashStep(dict *d)
int dictRehash(dict *d, int n) 将重哈希推进n步,每一步将ht[0]上的某一个dict entry链表上的dict entry移动到ht[1]上,根据ht[1]的sizemask重新继续算哈希值h
    dictEntry *de, *nextde;
    uint64_t h;
    
    for(de = d->ht[0].table[d->rehashidx]; de != NULL; de = nextde) {
        nextde = de->next;
        
        /* de插入到ht[1].table[h]链表头 */
        h = dictHashKey(d, de->key) & d->ht[1].sizemask;
        de->next = d->ht[1].table[h];
        d->ht[1].table[h] = de;
        d->ht[1].used++;
 
        d->ht[0].used--;
    }
    d->ht[0].table[d->rehashidx] = NULL;
    d->rehashidx++;

rehashidx记录ht[0]当前待迁移的dict entry链表位置,如果这个位置没有dict entry,则向后遍历ht[0].table数组,直到找到有数据的位置
如果一直找不都,则最多走empty_visits(n * 10)步
如果ht[0]上的数据都被迁移到ht[1]上了(dict->ht[0].used == 0),重哈希结束(rehashidx 置为-1),ht[0] free旧数据, 指向ht[1],ht[1] _dictReset置空

插入
int dictAdd(dict *d, void *key, void *val) 添加一条记录,如果key已存在则添加失败
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
int dictReplace(dict *d, void *key, void *val) 添加或更新一条记录

dictAddRaw
如果dict当前正在重哈希,把数据插到ht[1],否则插入到ht[0]
在对应bucket中插入数据时,总是头插,因为新数据接下来被访问的概率可能比较高,可以减少查找次数
_dictKeyIndex在dict中寻找插入位置,如果dict当前没有在重哈希,则只查找ht[0],否则查找ht[0]和ht[1]
_dictKeyIndex可能触发dict内存扩展,_dictExpandIfNeeded将哈希表长度扩展为原来2倍 dictExpand(d, d->ht[0].used*2)

_dictKeyIndex
根据key计算hash值h
遍历bucket d->ht[0].table[h], 查找已经有相同key dictCompareKeys,如果有则返回-1,如果没有则返回h

dictReplace
先尝试添加,如果失败则再次查找已存在的key,改变它的value dictSetHashVal,调用dictFreeEntryVal析构旧value 

删除
static int dictDelete(dict *ht, const void *key) 查找并删除一个元素
dictEntry *dictUnlink(dict *ht, const void *key)
static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree)

dictDelete 
    dictEntry *de, *prevde;
    de = ht->table[h];
    prevde = NULL;
    while(de) {
        if (dictCompareHashKeys(ht,key,de->key)) {
            /* Unlink the element from the list */
            if (prevde)
                prevde->next = de->next;
            else
                ht->table[h] = de->next;

            dictFreeEntryKey(ht,de);
            dictFreeEntryVal(ht,de);
            free(de);
            ht->used--;
            return DICT_OK;
        }
        prevde = de;
        de = de->next;
    }


dictGenericDelete
辅助dictDelete和dictUnlink的函数,它的功能是搜索并删除,要注意的是dictDelete也会触发推进一步重哈希。如果当前不在重哈希过程中,它只在ht[0]中查找要删除的key;否则ht[0]和ht[1]它都要查找。删除成功后会调用key和value的析构函数(keyDestructor和valDestructor)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值