Redis设计与实现-04-字典

《Redis设计与实现》黄建宏版的读书笔记

哈希表

  • 哈希表(hash table):又叫散列表,是根据关键码值进行访问的数据结构。将关键码值映射到表中的一个位置来访问,以加快查找的速度。这个函数映射叫做哈希函数,存放记录的数组叫做散列表。
  • 哈希表常用于通过key快速的找到对应的value时使用。
  • 哈希表的负载因子等于实际元素数目/哈希表的容量,负载因子越大表示冲突越大,负载因子越小,表示空间越浪费。一般负载因子位于0-0.7。
  • 哈希算法:当将一个新的键值对添加到字典里面时,先根据键值对的键计算出哈希值和索引值,然后根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上面。
  • 键冲突:当有两个或以上数量的键被分配到了哈希表数组的同一个索引上时,就称为键冲突,Redis使用的是链地址法来解决冲突。dictht中没有链表表尾的指针,为了速度考虑,Redis将新增加的节点放到链表的表头位置。
  • 为了让哈希表的负载因子维持在一个合理的范围内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩(通过rehash完成)。
  • Redis执行rehash的步骤如下:
    1. 为ht[1]哈希表分配空间,空间大小取决于要执行的操作和ht[0]当前包含的键值对。
      • 如果是扩展操作,ht[1]的大小为第一个大于等于ht[0].used * 2 的 2的n次方。
      • 如果是收缩操作,ht[1]的大小为第一个大于等于ht[0].used 的2的n次方。
    2. 将保存在ht[0]中的所有键值对rehash到ht[1]上面。
    3. 当ht[0]包含的所有键值对都迁移到ht[1]之后,释放ht[0],将ht[1]设置为ht[0],并将ht[1]新创建一个空白哈希表。
  • hash 表扩展的条件,BGSAVE、BGREWRITEAOF命令会产生新的子进程,大多数操作系统都是写时复制,为了避免在子进程期间进行哈希表的扩展操作节省内存,Redis会提高此时扩展操作所需要的负载因子。
    1. 服务器没有执行BGSAVE、BGREWRITEAOF命令时,且哈希表的负载因子大于等于1。
    2. 服务器执行BGSAVE、BGREWRITEAOF命令时,且哈希表的负载因子大于等于5。
  • hash 收缩操作的条件:负载因子小于0.1时。
  • rehash动作并不是一次性完成的,而是渐进式的,详细步骤如下:
    1. 为ht[1]分配空间
    2. 维护一个索引计数器变量rehashidx,并将它的值设置为0
    3. 当字典执行添加、删除、查找或更新操作时,程序除了执行指定的操作外,还会顺带将ht[0]哈希表在rehashindx索引上的所有键值对rehash到ht[1],当完成后,程序将rehashidx属性的值增一
    4. 当ht[0]所有键值对都被rehash到ht[1]时,程序将rehashidx属性值设置为-1。
  • 渐进式rehash执行期间的哈希表的删除、查找、更新等操作会在两个哈希表上进行,如果在ht[0]中未找到该节点,则会继续在ht[1]中进行操作。添加操作会一律保存在ht[1]里面,ht[0]不会进行任何添加操作。
  • redis中哈希表的结构
typedef struct dictht{
    // 哈希表数组
    dictEntry **table;
    // 哈希表大小
    unsigned long size;
    // 哈希表大小掩码,用于计算索引值,总是等于size - 1
    unsigned long sizemask;
    // 已有节点数量
    unsigned long used;
}dictht;

// dictEntry结构
typedef struct dictEntry {
    // 键
    void *key;
    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;
    // 指向下个哈希表节点,形成链表,用来解决键冲突
    struct dictEntry *next;
}dictEntry;

字典

  • Redis中字典的结构
typedef struct dict {
    // 类型特定函数,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会用不同的字典设置不同的类型特定函数。
    dictType *type;
    // 私有数据,保存了需要传给哪些特定函数的可选参数。
    void *privdata;
    // 哈希表,一般情况只会使用ht[0],对ht[0]进行rehash时才会使用ht[1]
    dictht ht[2];
    // rehash 索引,记录了当前rehash的进度; 当rehash未进行时,值为-1
    int rehashidx;
} dict;

typedef struct dictType {
    // 计算hash函数的值
    unsigned int (*hashFunction)(const void *key);
    // 复制键的函数
    void *(*keyDup)(void *privdata, const void *key);
    // 复制值的函数
    void *(*valDup)(void *privdata, const void *obj);
    // 销毁键的函数
    void *(*keyDestructor)(void *privdata, const void *key);
    // 销毁值的函数
    void *(*valDestructor)(void *privdata, const void *obj);
    // 对比键的函数
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章