redis 字典

字典:(符号表)

字典就是一个存储kv的存储结构,类似与c++的map,redis数据库的底层就是使用字典实现的

除了数据库,字典也是哈希键的底层实现

 

字典使用哈希表实现,哈希表中存储的都是kv结构

typedef struct dictht {

    // 哈希表数组
    dictEntry **table;

    // 哈希表大小
    unsigned long size;

    // 哈希表大小掩码,用于计算索引值
    // 总是等于 size - 1
    unsigned long sizemask;

    // 该哈希表已有节点的数量
    unsigned long used;

} dictht;

sizemask和哈希值一起决定了这儿节点应该放在哪里,我们每一个哈希表节点都有一个next属性,这个可以解决链表冲突的问题,使得多个键值一样的可以连在一起

下面我们看一下哈希表节点的定义:

typedef struct dictEntry {

    //
    void *key;

    //
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;

    // 指向下个哈希表节点,形成链表
    struct dictEntry *next;

} dictEntry;

 

 

下面是字典的定义:

type主要是针对不同的类型,private是针对函数的参数

其中计算哈希值的函数就在type里面指向的

有个哈希表数组,ht[1]只有rehash的时候使用,rehashindex也是rehash的时候使用

typedef struct dict {

    // 类型特定函数
    dictType *type;

    // 私有数据
    void *privdata;

    // 哈希表
    dictht ht[2];

    // rehash 索引
    // 当 rehash 不在进行时,值为 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */

} dict;
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;

当加入一个键值的时候,我们先根据type里面的函数计算出哈希值,&mask计算出索引值,加入哈希表的指定索引中,

为了解决哈希表的冲突,我们使用拉链发,但是为了考虑效率,我们通常将新加入的节点放在最前面,不yongO(N)掺入

 

rehash:

哈希表的键值会不听的增多减少,为了让负载因子,维持在一个合理的范围,我们需要适当的进行扩展和收缩

  1. 为字典的 ht[1] 哈希表分配空间, 这个哈希表的空间大小取决于要执行的操作, 以及 ht[0] 当前包含的键值对数量 (也即是 ht[0].used属性的值):
    • 如果执行的是扩展操作, 那么 ht[1] 的大小为第一个大于等于 ht[0].used 2 的 2^n (2 的 n 次方幂);
    • 如果执行的是收缩操作, 那么 ht[1] 的大小为第一个大于等于 ht[0].used 的 2^n 2
  2. 将保存在 ht[0] 中的所有键值对 rehash 到 ht[1] 上面: rehash 指的是重新计算键的哈希值和索引值, 然后将键值对放置到 ht[1] 哈希表的指定位置上。
  3. 当 ht[0] 包含的所有键值对都迁移到了 ht[1] 之后 (ht[0] 变为空表), 释放 ht[0] , 将 ht[1] 设置为 ht[0] , 并在 ht[1] 新创建一个空白哈希表, 为下一次 rehash 做准备。

 

哈希表的扩展和收缩的条件:

1:如果没有执行BSAVE或者BGREWRITEAOF,并且负载因子大于等于1

2:如果执行BSAVE或者BGREWRITEAOF,并且负载因子大于等于5

这样设计是因为如果执行的话,会fork出新的进程,因为遵循写实复制,为了尽量避免写入内存进行复制,所以将负载因子提高一些

 

如果负载因子小0.1执行收缩

 

渐进事rehash:

因为哈希表的数据可能特别的多,所有rehash不是一次完成的,是多次分批完成的,这里就用到了reashindex,最开始rehashindex=0,表示对索引值0指向的复制,结束了,开始索引值1的,rehashindx+1,这个过程中如果查找的话,会先查找ht[0]->ht[1],添加的话都会添加大1里面,这样可能保证服务器正常的运作

 

posted @ 2017-06-24 00:43  柳下_MBX  阅读(1894)  评论(0编辑  收藏  举报