Redis源码解析(3)字典

字典是一种用来保存键值对的抽象数据结构,在字典中,一个键和一个值进行关联,这些关联的键和值称为键值对。

应用

存储哈希键(值是哈希结构的键,值的底层实现数据结构之一是使用字典)
哨兵模式,管理Master和Slave节点

字典的实现

redis的字典使用哈希表作为底层实现,一个哈希表包含了多个哈希表节点,每个节点就保存了字典中的一个键值对。
Redis中的key-value对,key的数据类型可以是字符串、整数、浮点数(整数和浮点数在底层当做字符串处理),value的数据类型可以是String、Hash、List、Set、SortedSet。

1. 哈希表dictht

哈希表结构定义:
【src/dict.h Redis5.0】

/*
 * 哈希表
 *
 * 每个字典都使用两个哈希表,从而实现渐进式 rehash 。
 */
typedef struct dictht {
    // 哈希表数组
    dictEntry **table;
    // 哈希表大小
    unsigned long size;
    // 哈希表大小掩码,用于计算索引值
    // 总是等于 size - 1
    unsigned long sizemask;
    // 该哈希表已存储的数据个数(链表中的数据,而不是占用的slot个数)
    unsigned long used;
} dictht;

sizemask为什么等于size-1?
因为Redis中根据key得到的哈希值非常大,不能直接用来当做索引值,因此用哈希值和数组容量做取余运算,就可以得到一个size范围内的值,这个值刚好可以当做索引。而size-1得到的值和哈希值做位与运算,得到的结果和哈希值和size做取余运算一样,并且位运算比取余运算快很多。这就是sizemask值的由来。Redis计算索引值的源码:idx=hash&ht[table].sizemask

一个空的哈希表:
在这里插入图片描述

2. 哈希表节点dictEntry

每个节点值都保存着一个
哈希表节点结构定义:
【src/dict.h Redis5.0】

/*
 * 哈希表节点
 */
typedef struct dictEntry {    
    // 键
    void *key;
    union {
        void *val;    //值,void*类型,保证了value可以是任意类型
        uint64_t u64; 
        int64_t s64;  //过期时间
    } v;
    // 指向下个哈希表节点,形成链表
    struct dictEntry *next;
} dictEntry;

含有两个节点的哈希表:
在这里插入图片描述

3. 字典dict

字典结构定义:
【src/dict.h Redis5.0】

/*
 * 字典
 */
typedef struct dict {
    // 该字段对应的特定操作函数(如键的销毁函数、值的销毁函数)
    dictType *type;
    // 该字典依赖的数据
    void *privdata;
    // 哈希表
    dictht ht[2];
    // rehash 标识,默认为-1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */
    // 目前正在运行的迭代器数
    int iterators; /* number of iterators currently running */
} dict;

含有两个实体节点的字典:
在这里插入图片描述

哈希算法

将新的键值对添加到字典里,需要先根据键值对的键计算出哈希值和索引值,再根据索引值,将包含新键值对的哈希表节点放到哈希表数组指定的索引上。
此外,redis使用链地址法解决哈希冲突,如下图所示,k1和k2冲突后,通过头插法组成链表:
在这里插入图片描述

字典扩容-渐进式rehash

  1. 申请内存的大小为当前Hash表容量的一倍
  2. 把新申请的内存地址赋值给ht[1],并将rehashidx值设置为0表示开始重新计算哈希值(称为rehash)
  3. 在rehash期间,对字典执行添加、删除、查找或者操作时,程序除了执行这些操作外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1]上,然后rehashidx加1。在服务空闲期间,如果判断当前要进行rehash,也会对哈希表进行rehash。
  4. 随着字典执行增删改查操作,ht[0]中的所有键值对最终会被rehash到ht[1]上,这时将rehashidx设置为-1,表示rehash完成
  5. 对调ht[1]和ht[0]的值

部分源码

初始化字典

//dict.h

/* 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;
}
插入数据

//dict.h

/* Add an element to the target hash table */
/*
 * 将给定键值对添加到字典中
 * 只有给定键 key 不存在于字典时,添加操作才会成功
 * 
 * 添加成功返回 DICT_OK ,失败返回 DICT_ERR
 *
 * 最坏 T = O(N) ,均摊 O(1) 
 */
int dictAdd(dict *d, void *key, void *val)
{
    // 尝试添加键到字典,并返回包含了这个键的新哈希节点
    // T = O(N)
    dictEntry *entry = dictAddRaw(d,key);

    // 键已存在,添加失败
    if (!entry) return DICT_ERR;

    // 键不存在,设置节点的值
    // T = O(1)
    dictSetVal(d, entry, val);

    // 添加成功
    return DICT_OK;
}
/*
 * 尝试将键插入到字典中
 *
 * 如果键已经在字典存在,那么返回 NULL
 *
 * 如果键不存在,那么程序创建新的哈希节点,
 * 将节点和键关联,并插入到字典,然后返回节点本身。
 *
 * T = O(N)
 */
dictEntry *dictAddRaw(dict *d, void *key)
{
    int index;
    dictEntry *entry;
    dictht *ht;

    // 如果条件允许的话,进行单步 rehash
    // T = O(1)
    if (dictIsRehashing(d)) _dictRehashStep(d);

    /* Get the index of the new element, or -1 if
     * the element already exists. */
    // 计算键在哈希表中的索引值
    // 如果值为 -1 ,那么表示键已经存在
    // T = O(N)
    if ((index = _dictKeyIndex(d, key)) == -1)
        return NULL;

    // T = O(1)
    /* Allocate the memory and store the new entry */
    // 如果字典正在 rehash ,那么将新键添加到 1 号哈希表
    // 否则,将新键添加到 0 号哈希表
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
    // 为新节点分配空间
    entry = zmalloc(sizeof(*entry));
    // 将新节点插入到链表表头
    entry->next = ht->table[index];
    ht->table[index] = entry;
    // 更新哈希表已使用节点数量
    ht->used++;

    /* Set the hash entry fields. */
    // 设置新节点的键
    // T = O(1)
    dictSetKey(d, entry, key);

    return entry;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值