1_4_Redis设计与实现读书记-跳跃表

发布于 2022年 01月 16日 16:45

腾讯服务器

88 / 年

  • 上海/北京/广州...
  • 2核 2G 4M
  • Linux/Windows
新年大优惠

腾讯服务器

425 / 年

  • 上海/北京/广州...
  • 4核 8G 10M
  • Linux/Windows
年度最便宜

腾讯服务器

1249 / 年

  • 上海/北京/广州...
  • 8核 16G 14M
  • Linux/Windows
点击查看

跳跃表

跳跃表:(skiplist) 是一种有序数据结构,通过每个节点中维持多个指向其他节点的指针,达到快速访问节点的目的。支持平均O(logN)、最坏O(N)复杂度的节点查找,还可通过顺序性操作来批量处理节点。

特点:大部分情况,可媲美平衡树的效率。实现比平衡树简单。

Redis使用跳跃表作为有序集合键的底层实现之一。当集合包含元素比较多,或者元素的成员是比较长的字符串时。

Redis中只在两个地方用到了跳跃表:一是实现有序集合键;一个是在集群节点中用作内部数据结构。

1. 跳跃表的实现

Redis的跳跃表由zskiplistNode和zskiplist两个结构定义。zskiplistNode用于表示跳跃表节点,zskiplist表示保存跳跃表节点的相关信息。节点的数量以及指向表头节点和表尾节点的指针。

zskiplist

​ header: 指向跳跃表的表头节点

​ tail:指向跳跃表的表尾节点

​ level:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)

​ length:记录跳跃表的长度,即跳跃表目前包含节点的数量(表头节点不包含在内)

zskiplistNode

​ 层(level):每个层带有两个属性:前进指针和跨度。

​ 前进指针:用于访问位于表尾方向的其他节点

​ 跨度:记录了前进指针指向节点和当前节点的距离。

​ 后退(backward)指针:节点中使用BW字样标记节点的后退指针,指向位于当前节点的前一个节点。后退指针在程序重表尾向表头遍历时使用。

​ 分值(score):在跳跃表中,节点按各自所保存的分值从小到大排列。如上图中,各个节点中的1.0,2.0和3.0是节点所保存的分值。

​ 成员对象(obj):各个节点中的o1、o2和o3是节点所保存的成员对象。

**注意:**表头节点和其他节点的构造是一样的,表头节点也有后退指针、分值和成员对象,只是表头节点中的属性不会被使用到。

1.1 跳跃表节点

跳跃表节点的结构:

typedef struct zskiplistNode{
    //层
    struct zskiplistLevel{
        //前进指针
        struct zskiplistNode *forward;
        //跨度
        unsigned int span;
    } level[];
    //后退指针
    struct zskiplistNode *backward;
    //分值
    double score;
    //成员对象
    robj *obj;
}zskiplistNode;
1.1.1 层

level数组可以包含多个元素,元素中包含指向其他节点的指针。通过层可以加快访问其他节点的速度。层的数量越多,访问其他节点的速度就越快。

创建新跳跃表时,会根据幂次定律(power law,越大的数出现的概率越小)随机生成一个介于1和32之间的值作为level数组的大小,则为层的高度。

1.1.2 前进指针

每层都有一个指向表尾方向的前进指针(level[i].forward属性),用于从表头向表尾方向访问节点。

(1) 迭代程序先访问跳跃表的第一个节点(表头),然后从第四层的前进指针移动到表中的第二个节点。

(2) 在第二个节点时,程序沿着第二层的前进指针移动到表中的第三个节点。

(3) 在第三个节点时,程序同样沿着第二层的前进指针移动到表中的第四个节点。

(4) 再次沿着第四个节点的前进指针移动,程序碰到一个NULL,则此次遍历到达表尾,结束遍历。

1.1.3 跨度

层的跨度(level[i].span)用于记录两个节点间的距离。

​ (1) 跨度越大,距离越远

​ (2) 指向NULL的所有前进指针跨度都为0,因为没有连向任何节点。

1.1.4 后退指针

backward属性,用于从表尾向表头方向访问节点。每个节点只有一个后退指针,只能退至前一个节点。如下图BW节点所示:

1.1.5 分值和成员

节点的分值(score属性)是一个double类型的浮点数,节点均是按照从小到大来排序,多个节点的分值可以相同。

节点成员对象(obj属性)是一个指针,指向一个字符串对象,此对象保存着一个SDS值。对象必须是唯一的。

**注:**当多个节点的分值相同时,将按照对象obj在字典序中的大小进行排序。较小的对象所在的节点将会排在前面(靠近表头的方向)。

1.2 跳跃表

跳跃表结构:

typedef struct zskiplist{
    // 表头节点和表尾节点
    struct zskiplistNode *header, *tail;
    // 表中节点的数量
    unsigned long length;
    // 表中层数最大节点的层数
    int level;
}zskiplist;

通过使用zskiplist结构,可以更方便的对整个跳跃表进行处理,能够快速访问表头或表尾节点,能快速回去跳跃表节点的数量。

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

length 属性记录节点的数量,可以再O(1) 复杂度内返回表长度。

level 属性用于在O(1)复杂度内获取跳跃表中层高最大的节点的层数量。表头结点的层高不计算在内。

2. 跳跃表 API

3. 回顾

推荐文章