第四章 字典

在字典中,一个键(key)可以和一个值(value)进行关联(或者说将键映射为值),这些关联的键和值就称为键值对。

4.1 字典的实现

  Redis的字段使用哈希表作为底层实现。

  4.1.1 哈希表

typedef struct ditch{
    //哈希表数组
    dictEntry **table;
    
    //哈希表大小
    unsigned long size;

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

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

}dictht;

 

  table是一个数组,其中每个元素是指向一个哈希表节点的指针。

  size记录了table的大小。sizemask和哈希值一起决定一个键应该被放到table数组的哪个索引上。

  4.1.2 哈希表节点

  每个dictEntry结构保存着一个键值对。其中值可以是一个指针,或者uint64_t整数,或者int64_t整数。next属性指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接在一起,解决哈希冲突的问题。

typedef struct dictEntry{
    //
    void *key;
    
    //
    union{
        void *val;
        uint64_t u64;
        int64_t s64;
    }v;
 
    //指向下一个哈希表节点,形成链表
    struct dictEntry *next;

}dictEntry;

 

  4.1.3 字典

  Redis中的字典结构:

typedef struct dict{
    //类型特定函数
    dictType *type;

    //私有数据
    void *privdata;

    //哈希表
    dicht ht[2];

    //rehash 索引,当rehash不在进行时,值为-1  
    int trehashidx;
}dict

 

  type和privdata是为了多态字典而设计的,其中type指向了一个dictType结构,里面定一个了一系列操作键值对的函数,而privdata存放了函数需要的入参。

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属性是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表,一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在rehash时使用,rehashidx记录了rehash的进度。

4.2 哈希算法

  先计算key的哈希值,根据哈希值和sizemask计算索引值。根据索引值将键存放到指定位置。

4.3 解决键冲突

  当有两个及以上的键被分到同一个索引上时,产生了哈希冲突,Redis的解决方法是链地址法,将冲突的部分排成链表。因为没有指向链表表尾的指针,出于速度考虑,最新插入的放在表头位置。

4.4 rehash

  1. 为ht[1]分配空间
    1. 如果是扩展操作,取第一个大于等于ht[0].used*2的2的n次方幂
    2. 如果是收缩操作,取第一个大于等于ht[0].used的2的n次方幂
  2. 将ht[0]中的键值对重新映射到ht[1]中
  3. 将ht[0]空间释放,将ht[1]设置为h[0],将ht[0]设置为ht[1],并在ht[1]处创建一个新的空白哈希表,为下次rehash做准备

4.5 渐进式rehash

  渐进是指当键值对太多的情况,并不会一次全部重新分配完。

  1. 为ht[1]分配空间,字典同是持有ht[0]和ht[1]两个哈希表
  2. 将rehashidx初始化为0
  3. 每次对字典执行增删改查操作时,程序还会额外将ht[0]表在rehashidx索引上所有的键值对rehash到ht[1]中,当操作完毕时,将rehashidx+1
  4. 最终,ht[0]的所有键值对全部迁移完,将rehashidx重置为-1

  在渐进rehash期间,新增的键值对,一律保存在ht[1]中。删改查则先在ht[0]中查找,未找到再去ht[1]中查找。

 

posted @ 2021-02-21 15:05  walker993  阅读(45)  评论(0编辑  收藏  举报