• 沒有找到結果。

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 的应用则更加广泛:数据库、集群、哈希键、阻塞操作等功能都用到了这个算法。