你好,是我琉忆。
上一篇我们主要介绍了String和List的底层实现原理,今天我们来说说Hash的数据结构。
哈希作为我们常见的一种数据结构,那么在Redis中它是怎么实现的呢?
01Hash的数据结构
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我们在下一篇和集合一起讲解。
后话
是否觉得小小的哈希不简单。学完这篇文章后是否有耳目一新又重新认识了一次哈希?希望你能够继续跟着我的步伐往后看,下一篇我们讲讲集合的数据结构。
如果你觉得好,可以点个赞哦~
往期精选
PHP面试常考内容之Memcache和Redis(1)
PHP面试常考内容之Memcache和Redis(2)
PHP面试之面向对象(1)
PHP面试常考内容之面向对象(2)
PHP面试常考内容之面向对象(3)
如有疑问或想跟我交流,
可以加我的个人微信:leoyistar
关注琉忆编程库,PHP面试资料都在这
点击“好看”,让更多人看到~
4180

被折叠的 条评论
为什么被折叠?



