Redis中ZSET的底层数据结构

https://www.cnblogs.com/wuyizuokan/p/11108417.html

ZSet数据结构类似于Set结构,只是ZSet结构中,每个元素都会有一个分值,然后所有元素按照分值的大小进行排列,相当于是一个进行了排序的链表。

如果ZSet是一个链表,而且内部元素是有序的,在进行元素插入和删除,以及查询的时候,就必须要遍历链表才行,时间复杂度就达到了O(n),这个在以单线程处理的Redis中是不能接受的。所以ZSet采用了一种跳跃表的实现。这个实现有点类似于Kafka存储消息是使用的稀疏索引,kafka这个相对较简单,可以用来介绍类比学习。

如果熟悉Kafka,就知道Kafka在进行持久化的时候,生成了两个文件,一个是xxxxxxx.log,一个是xxxxxxx.index,这其中log文件中以链表的形式保存着消息的详细信息,而index文件中,则是保存着这些消息的索引,或者说偏移量,但又不是每一条消息的索引都在index文件中存在,而是稀疏的,比如log文件中的消息的索引从0-10000,那么index文件中存储的索引可能是100, 500, 700, 1000, 5000, 6500,每一个索引中都保存着对应的log文件中的消息的具体位置,如图:
在这里插入图片描述

当要访问偏移量为899的这条消息时,先去index文件中查找,找到了700和1000这个区间,根据700这个索引中的信息,找到log文件中700这条消息的具体位置,然后顺序往下查找,直到找到索引为899的这条消息为止。从这个实现中我们可以看到,Kafka并没有进行log文件的整个遍历,而是通过index中的稀疏索引,找到消息在log中的大概位置,然后顺序遍历找到消息,这样就大大提高了查找的效率,如图:

在这里插入图片描述

Redis的跳跃表和上面类似,只是更加复杂一些,Kafka的稀疏索引只有一层,而Redis的索引被提取为多层。如图:

在这里插入图片描述

所有的元素都会在L0层的链表中,根据分数进行排序,同时会有一部分节点有机会被抽取到L1层中,作为一个稀疏索引,同样L1层中的索引也有一定机会被抽取到L2层中,组成一个更稀疏的索引列表。

下面用图来演示一下在对快速链表进行插入、删除、查询时,是如何定位到L0层中的具体位置的。

首先,假定有这么一个链表,注意这里只展示分数,而不展示具体的值了:
在这里插入图片描述

如果要查找分数为66的元素,首先在L2层的索引找。很明显,66位于25和85中间,这时就缩小了查找区间:
在这里插入图片描述

然后根据获得的区间,去L1对应的区间中查找,得到一个更精确的区间:
在这里插入图片描述

最终,根据这个更精确的区间,去L0层顺序遍历,即可得到要查找的元素:
在这里插入图片描述

上述即是对Redis的跳跃表的原理的一个简述。

这种跳跃表的实现,其实和二分查找的思路有点接近,只是一方面因为二分查找只能适用于数组,而无法适用于链表,所以为了让链表有二分查找类似的效率,就以空间换时间来达到目的。

跳跃表因为是一个根据分数权重进行排序的列表,可以再很多场景中进行应用,比如排行榜,搜索排序等等。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值