Redis~有序结合(ZSet)的数据结构之跳表(SkipList )

有序集合(ZSet)

  • 有序集合类型 (SortedSet) 相比于集合类型多了⼀个排序属性 score(分值),所以对于有序集合ZSet 来说,每个存储元素相当于有两个值组成的,⼀个是有序结合的元素值,⼀个是分值。有序集合的存储元素值也是不能重复的,但分值是可以重复的。

  • 所以有序集合的排序是靠分值的大小进行排序的

  • 有序集合类型的底层数据结构可以是压缩列表(ZipList)或者跳表(SkipList )

  • 当有序集合对象的所有元素成员的长度都小于64字节,并且保存的元素数量小于128个时,使用压缩列表
    在这里插入图片描述
    在这里插入图片描述

  • 如果不满足上述条件中的任意一个,都会使用跳表**(这里的跳表是结合字典的)**
    在这里插入图片描述

  • 之所以这样设置是因为考虑到如果直接使用跳跃表,如果需要查找成员的分值时只能通过遍历成员来进行查找,而这样的效率是O(logN)

  • 而字典虽然建立映射后可以O(1)的查找到分值,但是哈希表只能通过key值进行查找,并不支持范围查询。

  • 所以将两者进行结合,使用字典建立起元素与分值的映射,使用字典来进行成员分数的查找,而使用跳跃表来进行范围型操作,这样就很好的解决了这个问题。

  • 毕竟如果是单个查询, 使用哈希是最好的处理办法, 如果是范围查询使用跳表是最好的办法

跳跃表介绍

  • 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的
  • 跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点
  • 在大部分情况下,跳跃表的效率可以和平衡树相媲美,并且因为跳跃表的实现比平衡树要来得更为简单,所以有不少程序都使用跳跃表来代替平衡树
  • Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员(member)是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现
  • Redis只在两个地方用到了跳跃表:
    一个是实现有序集合键,另一个是在集群节点中用作内部数据结构

跳跃表的总体实现

  • Redis的跳跃表由redis.h/zskiplistNode和redis.h/zskiplist两个结构定义
  • 其中zskiplistNode结构用于表示跳跃表节点
  • 而zskiplist结构则用于保存跳跃表节点的相关信息,比如节点的数量,以及指向表头节点和表尾节点的指针等等
    在这里插入图片描述
跳跃表节点(struct zskiplistNode)
typedef struct zskiplistNode {
    // 成员对象
    robj *obj;

    // 分值
    double score;

    // 后退指针
    struct zskiplistNode *backward;

    // 层
    struct zskiplistLevel {

        // 前进指针
        struct zskiplistNode *forward;

        // 跨度
        unsigned int span;

    } level[];

} zskiplistNode;

该结构包含以下属性:

  • 成员对象(obj):各个节点中的o1、o2和o3是节点所保存的成员对象
  • 分值(score):各个节点中的1.0、2.0和3.0是节点所保存的分值。在跳跃表中,节点按 各自所保存的分值从小到大排列。
  • 后退(backward)指针:节点中用BW字样标记节点的后退指针,它指向位于当前节点 的前一个节点。后退指针在程序从表尾向表头遍历时使用。
  • 层(level):节点中用L1、L2、L3等字样标记节点的各个层,L1代表第一层,L2代表 第二层,以此类推。每个层都带有两个属性:前进指针和跨度。前进指针用于访问位于表尾 方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的距离。在上面的图片 中,连线上带有数字的箭头就代表前进指针,而那个数字就是跨度。当程序从表头向表尾进 行遍历时,访问会沿着层的前进指针进行。也就是前进指针指向下面测层, 而跨度就是前进到多少层
  • 跳跃表的查询从最顶层出发,通过前进指针来往后查找,通过比较节点的分数来判断当前节点是否与索引匹配,如果查找不到则进入下层继续查找,并记录下跨越的层数span来进行排位。
  • 同时为了处理特殊情况,还准备了一个后退指针来进行从表尾到表头的遍历,但是与前进不同,后退指针并不存在跳跃,而是只能一个一个向后查询
跳跃表(struct zskiplist)
  • zskiplist结构用于保存跳跃表节点的相关信息,比如节点的数量,以及指向表头节点和表尾节点的指针等等
typedef struct zskiplist {
    structz skiplistNode *header, *tail;//表头节点和表尾节点
    unsigned long length;               //表中节点的数量
    int level;                          //表中层数最大的节点的层数
} zskiplist;

zskiplist结构的属性如下:

  • header和tail指针分别指向跳跃表的表头和表尾节点,通过这两个指针,程序定位表头节点和表尾节点的复杂度为O(1)

  • 通过使用length属性来记录节点的数量,程序可以在O(1)复杂度内返回跳跃表的长 度

  • level属性则用于在O(1)复杂度内获取跳跃表中层高最大的那个节点的层数量,注意表头节点的层高并不计算在内, 并且(为了避免因为层数过高导致的大量空间损失,Redis跳跃表的节点高度最高位32层。)

  • 仅靠多个跳跃表节点就可以组成一个跳跃表,如下图所示:
    在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值