介绍
字典又称为符号表(symbol table)、关联数组(associative array)或映射(map),是一种用于保存键值对(key-value pair)的抽象数据结构。例如:redis中的所有key到value的映射,就是通过字典结构维护,还有hash类型的键值。
通过redis中的命令感受一下哈希键:
127.0.0.1:6379> HSET user name Mike
(integer) 1
127.0.0.1:6379> HSET user passwd 123456
(integer) 1
127.0.0.1:6379> HSET user sex male
(integer) 1
127.0.0.1:6379> HLEN user //user就是一个包含3个键值对的哈希键
(integer) 3
127.0.0.1:6379> HGETALL user
1) "name"
2) "Mike"
3) "passwd"
4) "123456"
5) "sex"
6) "male"
字典的实现
redis的字典是由哈希表实现的,一个哈希表有多个节点,每个节点保存一个键值对。
定义
//哈希表
typedef struct dictht {
//存放一个数组的地址,数组存放着哈希表节点dictEntry的地址。
dictEntry **table;
//哈希表table的大小,初始化大小为4
unsigned long size;
//用于将哈希值映射到table的位置索引。它的值总是等于(size-1)。
unsigned long sizemask;
//记录哈希表已有的节点(键值对)数量
unsigned long used;
} dictht;
//哈希表的节点
typedef struct dictEntry {
//key
void *key;
//value
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
//指向下一个hash节点,用来解决hash键冲突(collision)
struct dictEntry *next;
} dictEntry;
//字典
typedef struct dict {
//指向dictType结构,dictType结构中包含自定义的函数,这些函数使得key和value能够存储任何类型的数据。
dictType *type;
//私有数据,保存着dictType结构中函数的参数。
void *privdata;
//两张哈希表。
dictht ht[2];
//rehash的标记,rehashidx==-1,表示没在进行rehash
long rehashidx;
//正在迭代的迭代器数量
int iterators;
} dict;
图示
rehash
- 扩展或收缩
- 扩展:ht[1]的大小为第一个大于等于ht[0].used * 2的 2n2n 。
- 收缩:ht[1]的大小为第一个大于等于ht[0].used的 2n2n 。
- 将所有的ht[0]上的节点rehash到ht[1]上。
- 释放ht[0],将ht[1]设置为第0号表,并创建新的ht[1]。
- 渐进式rehash
- 字典结构dict中的rehashidx为-1时表示不进行rehash,当rehashidx值为0时,表示开始进行rehash。
- 删除、更新、查找在ht[0]和ht[1]上都进行;新增只在ht[1]上操作;保证ht[0]上的数据只减不删。
- 当rehash时进行完成时,将rehashidx置为-1,表示完成rehash。
hash冲突
- 通过链接法(chaining)来解决冲突
- dictEntry 节点组成的链表是个单向链表,没有指向链表表尾的指针, 所以为了速度考虑, 程序总是将新节点添加到链表的表头位置(复杂度为 O(1))