每一个链表节点使用一个adlist.h/listNode结构表示:

typedef struct listNode{
//前置节点
struct listNode *prev;
//后置节点
struct listNode *next;
//节点值
void *value;
};
链表
typedef struct list{
//表头节点
listNode *head;
//表尾节点
listNode *tail;
//链表所包含的节点数目
unsigned long len;
//节点复制函数
//节点释放函数
//节点值对比函数
}list;

字典

字典是一种用于保存键值对(key-value-pair)的抽象数据结构。在字典中一个键可以和一个值进行关联,来确定唯一。

  • redis数据库就是使用字典作为底层数据结构实现的。对数据库的增删查改也是基于字典
  • 字典也是哈希键底层实现(例如hashmap)

1.哈希表

redis字典所使用的是hash表由dict.h/dictht结构定义

typedf struct dictht{

//哈希表数组

dicEntry **table;

//哈希表大小

unsigned long size;

//哈希表掩码,用于计算索引值

unsigned long marksize;//其值总是size-1

//该哈希表所拥有的节点数目

unsigned long used;

}dictht;

marksize是哈希表大小掩码,值为size-1,通过marksize以及对象hash值确定桶的位置。

这里哈希表大小是数组大小  而不是节点数目大小。

2.哈希表节点dictentry

typedef struct dictentry{
void *key;
union{
void *val;
uint64_t u64;
int64_t s64;
}v;
struct dictentry next;
}dictentry;

key属性保存着键值对中的键,而v属性则保存着键值对中的值,其中键值对的值可以是一个指针,或者是一个uint64_t 再或者是int64_t整数。

next属性是指向另一个hash表节点的指针,这个指针可以将多个哈希值相同的键值对都连接在一起,解决hash冲突问题 链式

3.字典的实现

字典采用的是连地址法来解决冲突问题 并且采用的是头插法

dict{

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

void *privdata;//私有数据

dictht ht[2]//哈希表两个

//rehash索引

//当rehash不再进行的时候 rehash=-1

int rehashindex = -1;

}dict;

特定函数和私有数据都是实现多态的字典。

dictType保存的是不同类型键值对的操作函数。 

privdata保存的是特定操作函数的参数。

dict是字典 dictht是哈希表 dictentry是键值对

个人感悟就是dictht就像是一个hashmap,而且还是采用头插法,这就意味着无法多个线程进行性扩容或者是收缩。

索引位置的计算:根据键值对中的键计算出hash值,然后在根据索引值,计算出索引位置,将包含键值对的dictentry 键值对节点放置在哈希表数组指定索引上。

 

 

Q:何时进行哈希表的扩展与收缩(当满足以一下条件的任何一个 程序就会对哈希表进行扩展操作)

  1. 服务器目前没有在执行bgsave命令或者是bgrewriteaof命令,并且哈希表的负载因子大于等于1
  2. 服务器目前正在执行bgsave命令或者是bgrewriteaof命令,但是哈希表的负载因子大于等于5

负载因子的计算 = 哈希表已保存的节点数目/哈希表的数组大小

ht[0].used/ht[0].size

 

rehash大概流程

为了让负载因子在一个合适的范围内,适当的时候要对哈希表进行rehash

  1. 为字典的哈希表ht[1]分配空间,这个哈希表的空间大小是由正在执行的操作和当前哈希表已经保存的节点数目决定的(尽量保证rehash后的哈希表一桶一坑 减少hash冲突)
  • 如果正在执行的扩容操作:2^n>used*2  找到第一个比used*2大的次方数
  • 如果正在执行的收缩操作:2^n>used      找到第一个比used大的次方数

2.将保存在ht[0]中的所有键值对 rehash到ht[1]上面来,rehash要重新计算位置

3.移动

 

渐进式rehash

  1. 为ht[1]分配空间,让字典同时拥有两个哈希表。
  2. 在字典中维持一个索引计数器变量rehashidx,并且设置为0,表示rehash正在开始
  3. 在rehash进行期间,每次对字典执行的增删改查,程序执行指定操作以为都会将该键值对移至ht[1],当移动完成后rehashidx++
  4. 随着字典操作的不断执行,最终会在某一个时刻 ht[0]上所有的键值对都移动到ht[1]处,将rehashidx值至为-1

因为在执行rehash时候,该字典保存的键值对分布在两个hash表中,所以执行操作时候,先查询ht[0]   后查询ht[1],如果执行的是增加操作的话,就直接添加到ht[1]处,保证ht[0]哈希表数目只减少不增加