字典就相当于一个hashmap,了解字典需要从下面几个问题开始
1.字典的结构
2. 字典的扩容的机制
3. 扩容时的请求处理
4. 字典是怎么添加值的
字典的结构
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
typedef struct dictType {
uint64_t (*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;
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2]; //之所以有两个dictht是为了rehash
long rehashidx; //表示当前rehash的位置,-1表示当前没有进行rehash
unsigned long iterators; /* number of iterators currently running */
} dict;
看上面结构可以知道字典和hashMap结构很相似,其中dictEntry 就对应着一个key-value, dictht 中的**table就是一个dictEntry 数组的引用。dictht有一个used表示当前dictht中key-value对的个数。另外
dict有两个dictht ,dictht[0], dictht[1],正常情况下只会用到dictht[0],rehash时才会用到dictht[1]. 字典是开链法解决键值冲突。
字典的扩容的机制
1、字典的什么时候进行扩宽?
当负载因子等于 1的时候,如果没在执行BGSAVE和BGREWRITEAOP时,就会进行扩容。如果在 在执行BGSAVE和BGREWRITEAOP,当负载因子达到5时,也会进行扩容。
另外说下一,当负载因子等于0.1时会进行收缩
2、字典扩容后的size
字典是2倍扩容,如果扩容后的大小min(2*size,DICT_HT_INITIAL_SIZE),即扩容后至少为DICT_HT_INITIAL_SIZE,DICT_HT_INITIAL_SIZE等于4是字典初始化默认大小
3、扩容大概流程
- 扩容时dictht[1].size = min(2*dictht[0].size,DICT_HT_INITIAL_SIZE)为dictht[1]分配空间
- dictht[0]的数据迁移到dictht[1]
- 迁移完成后dictht[0]=dictht[1], dictht[1]=null
4、渐进式扩容
dictht[0]的数据迁移到dictht[1]这个过程不是一次完成的,而是分为多次,渐进式的完成的。其中有一个rehashidx表示当前字典正在迁移的位置table[rehashidx]之前的都已经迁移完成了
5、扩容后链表的顺序问题
字典扩容rehash过程是把值加到链表的头部,所以会是的中值的顺序和原来的不一样。
扩容时的请求处理
当前进行rehash扩容时,维持着两个dictht。请求操作分为读和写。写的操作都在dictht[1]上执行。
读操作会先读dictht[0],再读dictht[1]。
字典添加值过程
1、判断是否正在执行rehash过程,如果是则对dictht[1]操作,否则对dictht[0]操作
2、先通过hash找到在table中 对应的位置
3、如果对应的位置位null,则直接放入
4、否则遍历链表,如果没有在链表里面,则放到链表的最开头位置。