1.3 字典
1.3.2 字典的实现
实现字典的方法有很多种:
• 最简单的就是使用链表或数组,但是这种方式只适用于元素个数不多的情况下;
• 要兼顾高效和简单性,可以使用哈希表;
• 如果追求更为稳定的性能特征,并且希望高效地实现排序操作的话,则可以使用更为复 杂的平衡树;
在众多可能的实现中,Redis 选择了高效且实现简单的哈希表作为字典的底层实现。
dict.h/dict 给出了这个字典的定义:
/*
* 字典
*
* 每个字典使用两个哈希表,用于实现渐进式 rehash
*/
typedef struct dict { // 特定于类型的处理函数 dictType *type;
// 类型处理函数的私有数据 void *privdata;
// 哈希表(2 个)
dictht ht[2];
// 记录 rehash 进度的标志,值为-1 表示 rehash 未进行 int rehashidx;
// 当前正在运作的安全迭代器数量 int iterators;
} dict;
以下是用于处理 dict 类型的 API ,它们的作用及相应的算法复杂度:
14 Chapter 1. 内部数据结构
Redis 设计与实现, 第一版
操作 函数 算法复杂度
创建一个新字典 dictCreate O(1)
添加新键值对到字典 dictAdd O(1)
添加或更新给定键的值 dictReplace O(1) 在字典中查找给定键所在的节点 dictFind O(1) 在字典中查找给定键的值 dictFetchValue O(1) 从字典中随机返回一个节点 dictGetRandomKey O(N ) 根据给定键,删除字典中的键值对 dictDelete O(1)
清空并释放字典 dictRelease O(N )
清空并重置(但不释放)字典 dictEmpty O(N )
缩小字典 dictResize O(N )
扩大字典 dictExpand O(N )
对字典进行给定步数的 rehash dictRehash O(N ) 在给定毫秒内,对字典进行 rehash dictRehashMilliseconds O(N ) 注意 dict 类型使用了两个指针分别指向两个哈希表。
其中,0 号哈希表(ht[0])是字典主要使用的哈希表,而 1 号哈希表(ht[1])则只有在程序 对 0 号哈希表进行 rehash 时才使用。
接下来两个小节将对哈希表的实现,以及哈希表所使用的哈希算法进行介绍。
哈希表实现
字典所使用的哈希表实现由 dict.h/dictht 类型定义:
/*
* 哈希表
*/
typedef struct dictht {
// 哈希表节点指针数组(俗称桶,bucket)
dictEntry **table;
// 指针数组的大小 unsigned long size;
// 指针数组的长度掩码,用于计算索引值 unsigned long sizemask;
// 哈希表现有的节点数量 unsigned long used;
} dictht;
table 属性是一个数组,数组的每个元素都是一个指向 dictEntry 结构的指针。
每个 dictEntry 都保存着一个键值对,以及一个指向另一个 dictEntry 结构的指针:
/*
* 哈希表节点
*/
// 值 union {
void *val;
uint64_t u64;
int64_t s64;
} v;
// 链往后继节点
struct dictEntry *next;
} dictEntry;
next 属性指向另一个 dictEntry 结构,多个 dictEntry 可以通过 next 指针串连成链表,从 这里可以看出,dictht使用链地址法来处理键碰撞:当多个不同的键拥有相同的哈希值时,哈 希表用一个链表将这些键连接起来。
下图展示了一个由 dictht 和数个 dictEntry 组成的哈希表例子:
dictht table size: 4 sizemask: 3
used: 3 key1 value1 next
dictEntry key2 value2 next
dictEntry key3 value3 next
NULL
rehashidx: -1 iterators: 0
dictht table size: 4 sizemask: 3
used: 3 ht[0]
dictht table size: 0 sizemask: 0
used: 0 key1 value1 next
dictEntry key2 value2 next
dictEntry key3 value3 next
NULL
NULL
NULL
NULL
16 Chapter 1. 内部数据结构
Redis 设计与实现, 第一版
在上图的字典示例中,字典虽然创建了两个哈希表,但正在使用的只有 0 号哈希表,这说明字 典未进行 rehash 状态。
哈希算法
Redis 目前使用两种不同的哈希算法:
1. MurmurHash2 32 bit 算法:这种算法的分布率和速度都非常好,具体信息请参考 Mur-murHash 的主页:http://code.google.com/p/smhasher/。
2. 基 于 djb 算 法 实 现 的 一 个 大 小 写 无 关 散 列 算 法: 具 体 信 息 请 参 考 http://www.cse.yorku.ca/~oz/hash.html。
使用哪种算法取决于具体应用所处理的数据:
• 命令表以及 Lua 脚本缓存都用到了算法 2 。
• 算法 1 的应用则更加广泛:数据库、集群、哈希键、阻塞操作等功能都用到了这个算法。