你好,是我琉忆。
上一篇我们主要介绍了String和List的底层实现原理,今天我们来说说Hash的数据结构。
哈希作为我们常见的一种数据结构,那么在Redis中它是怎么实现的呢?
01
Hash的数据结构
Redis 中的hash,内部是由 HashTable 或者 ziplist实现的。而HashTable 的内部结构是由数组加链表的二维结构实现的。它包含若干个 key-value,key 不重复。
首先我们来看看HashTable内部实现结构:
实现的流程:
实现的源码:
/* * 哈希表节点 */typedef struct dictEntry { // 键 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v; // 指向下个哈希表节点,形成链表 struct dictEntry *next;} dictEntry;/* * 哈希表 * * 每个字典都使用两个哈希表,从而实现渐进式 rehash 。 */typedef struct dictht { // 哈希表数组 dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩码,用于计算索引值 // 总是等于 size - 1 unsigned long sizemask; // 该哈希表已有节点的数量 unsigned long used;} dictht;
那么HashTable是如何实现扩容的呢?
02
HashTable的扩容流程
扩容流程如下:
首先我们要知道,Redis 扩容采用的是渐进式 Hash 的方式进行扩容。原来的 hash 表是 h[0],然后加倍 size 生成一个新的 hash 表 h[1],然后在定时任务中,渐渐地将 h[0] 中的内容迁移到 h[1] 中。
执行下面的操作时,内部的实现步骤:
- 读操作:先去 h[0] 中找,找不到再去 h[1] 中去找。
- 写操作:直接写在 h[1] 中
- 删除操作和读操作类似
根据上面的流程,我们来按步骤看看它的内部扩容实现:
需要知道的是:哈希表内部是由数组加链表实现的。
步骤一:新建一个 H[1],为字典 h[1] 分配空间,hash 表初始化的状态就有一个 h[0] 和 h[1]。
int _dictInit(dict *d, dictType *type, void *privDataPtr){ // 初始化两个哈希表的各项属性值 // 但暂时还不分配内存给哈希表数组 _dictReset(&d->ht[0]); _dictReset(&d->ht[1]); // 设置类型特定函数 d->type = type; // 设置私有数据 d->privdata = privDataPtr; // 设置哈希表 rehash 状态 d->rehashidx = -1; // 设置字典的安全迭代器数量 d->iterators = 0; return DICT_OK;}
具体的实现在 dict.c 中的 dictExpand 这个方法。此时 dicth[1] 指向一个数组table[]。
哈希表初始状态:
为 h[1] 分配空间:
步骤二:将 h[0] 中的所有键值对,rehash 到 h[1] 上,rehash 是指重新计算哈希值和索引值。将键值对放到 h[1] 上面的指定位置。
步骤三:当h[0] 中包含的键值对都迁移到了 h[1] 上面,需要释放 h[1],并将 h[1] 设置为 h[0],并在 h[1] 新创建一个空白哈希表,为下次 rehash 准备。
自此Hash实现扩容。这就是哈希表完成一个完整的扩容过程。
zplist我们在下一篇和集合一起讲解。
后话
是否觉得小小的哈希不简单。学完这篇文章后是否有耳目一新又重新认识了一次哈希?希望你能够继续跟着我的步伐往后看,下一篇我们讲讲集合的数据结构。