Redis 数据结构(三)—— 字典

关于字典这个数据结构的内容就稍微的有那么一点多了,redis数据库就可以看成是一个字典,那我们就来看看字典的内部究竟是如何实现的吧~

1 字典的实现

Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。接下来我们一个一个的来说。

1.1 哈希表

首先,哈希表的结构大概长下边这个鬼样子:
在这里插入图片描述

  • table: dictEntry类型的数组
  • size:哈希表的大小
  • sizemask:用于计算索引的掩码,永远等于size-1,和哈希值一起决定一个键放到table的哪个位置
  • used:已经有的节点数量
    举个实际的例子呢,就长下边这个样子:
    在这里插入图片描述

1.2 哈希表节点(dictEntry)

哈希表中的每个节点是dictentry类型,这是个什么类型呢,他大概长下边这个鬼样子:
在这里插入图片描述

  • key属性:保存着键值对中的键
  • v属性:保存着键值对中的值,其中键值对的值可以是一个指针,或者是一个uint64_ t整数,又或者是一个int64_ t整数。
  • next属性:next属性是指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接在一起,以此来解决键冲突( collision)的问题。举个例子就是下边这个情况:
    在这里插入图片描述

1.3 字典结构

终于,铺垫了半天大哥终于来了,让我们一睹真容吧:
在这里插入图片描述

  • type属性:一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis 会为用途不同的字典设置不同的类型特定函数。至于dictType大概长这个样子:
    在这里插入图片描述
  • privdata属性:则保存了需要传给那些类型特定函数的可选参数。
  • ht属性:是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表,一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。rehash的事情之后再说。
  • rehashidx:它记录了rehash目前的进度,如果目前没有在进行rehash,那么它的值为-1。
    老规矩看个整体图吧!
    在这里插入图片描述

2 哈希算法

这个算法有啥用呢? 简单来说我们现在要想字典里放键值对,放哪?我们要解决的就是放哪的问题,首先呢字典具有内置哈希函数会根据当前的键值对计算哈希值,接下来再根据和哈希值和刚才我们介绍的sizemask来计算我们哈希表的索引,也就是来放这个键值对的具体地方。具体来说就是如下语句:

hash = dict->type->hashFunction(k0);
index = hash & dict->ht[0].sizemask

3 解决键冲突

首先,啥叫冲突呢?就是两个键值对经过计算都想放到同一个索引(index)这就是冲突。Redis的解决办法就是链地址法,就是大家都想在这就在这,排队呗,通过next指针形成一个链表。就是下边这样子:
在这里插入图片描述

4 rehash

这是刚才没讲的一个知识点,字面理解就是重新做hash,为啥要重新做呢?原因有二:

  • 哈希表用的太少了,浪费空间,重新搞得的时候搞小点。
  • 哈希表用的太多了,都要满了,再插入新的就得通过next指针排队,那就会影响哈希表的性能了。
    如此一来。我们就必须在特定情况下做rehash,那问题来了啥时候搞呢?

4.1 扩展时机

当以下条件中的任意一个被满足时,程序会自动开始对哈希表执行扩展操作:

  • 服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于1。
  • 服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5。
    负载因子计算方式如下:
// 负载因子 = 哈希表已保存节点数量/哈希表大小
load_factor = ht[0].used /ht[0].size

注意: 根据BGSAVE命令或BGREWRITEAOF命令是否正在执行,服务器执行扩展操作所需的负载因子并不相同,这是因为在执行BGSAVE命令或BGREWRITEAOF命令的过程中,Redis需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制(copy-on-write)技术来优化子进程的使用效率,所以在子进程存在期间,服务器会提高执行扩展操作所需的负载因子,从而尽可能地避免在子进程存在期间进行哈希表扩展操作,这可以避免不必要的内存写人操作,最大限度地节约内存。

4.2 收缩时机

当哈希表的负载因子小于0.1时,程序自动开始对哈希表执行收缩操作。

4.3 rehash过程

时机知道了,那具体咋办呢??

  • 为字典的ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(也即是ht [0].used属性的值):
    • 如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht [0].used*2的2^n(2的n次方幂);
    • 如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht [0] .used的2^n。
  • 将保存在ht[0]中的所有键值对rehash到ht[1]上面: rehash指的是重新计
    算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上。
  • 当ht[0]包含的所有键值对都迁移到了ht[1]之后(ht[0]变为空表),释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备。

4.4 渐进rehash

这个简单,就是如果有很多东西需要搬家,一次是搬不完的。我们平时还要送外卖,我要是光搬家,就甭干别的了没饭吃了。所以说rehash的过程也是每次搞一点,搞完为止。具体流程如下:

  • 1)为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表。
  • 2)在字典中维持- 一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash工作正式开始。
  • 3)在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成之后,程序rehashidx属性的值增一。
  • 4)随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash至ht[1],这时程序将rehashidx属性的值设为-1,表示rehash操作已完成。

5 字典常用API

在这里插入图片描述
在这里插入图片描述
好了,今天又分享知识了哦~

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从前慢慢慢死了

打钱!一分也行啊!!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值