Redis数据结构学习-跳表(四)

跳表

跳表 skiplist 是一种有序的数据结构, 通过在每个节点中维持多个指向其它节点的指针、达到快速访问节点的目的

支持平均 O(logN), 最坏 O(N)复杂度的查找, 还支持顺序性操作来批量处理节点, 大部分情况下、效率可以合平衡树相媲美, 且实现简单, 很多程序都可以使用skipList替代平衡树, Redis使用跳跃表作为有序集合键的底层实现之一.

跳表只在两个地方使用: 实现有序集合键 & 在集群节点中用作内部结构

跳表的实现

Redis 的跳跃表由 redis.h/zskiplistNoderedis.h/zskiplist 两个结构定义, zskiplistNode 结构用于表示跳跃表节点, zskiplist 则用于保存跳跃表节点的相关信息, eg.

节点梳理、及指向表头节点 和 表尾节点的指针.

跳表示意图.png

最左边是跳表的zskiplist结构, 包含下边这些属性:

header: 指向跳表的表头节点

tail: 指向跳表的表尾节点

level: 记录当前跳表内、层次最大的那个节点的层数(表头节点的层数不计算在内)

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

右边是4个zskiplistNode结构, 属性如下:

level: 节点中用 L1, L2, L3 等标记节点的各个层, L1代表第一层, 依次类推. 每个层有两个属性: 前进指针跨度. 前进指针用于访问位于表尾方向的其它节点, 跨度记录前进指针指向的节点和当前节点的距离. 程序从表头向表尾遍历时, 访问会沿着层的前进指针进行.

backward: 后退指针, 节点中BW标记的指针, 指向当前节点的前一个指针, 后退指针在程序从表尾向表头遍历时使用

score: 分值, 节点中的1.0, 2.0, 3.0是各节点保存的分值, 在跳表中节点按各自保存的分值从小到大排列.

obj: 成员对象, 各个节点中的o1, o2 和 o3 是节点保存的成员对象.

注意: 表头节点和其它节点的构造是一致的, 也有后退指针, 分值 和 成员对象, 但不会被使用.

跳跃表节点

redis.h/zskiplistNode结构定义

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

跳跃表节点的level数组可以包含多个元素, 每个元素都包含一个指向其它节点的指针, 程序可以通过这些层来加速访问其它节点的速度, 一般来说、层的数量越多, 访问其它节点的速度就越快. 每次创建一个新的跳表时、程序根据幂次定律随机生成一个介于1和32之间的值作为level数组的大小, 即 层高

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

跨度 (level[i].span)用于记录两个节点之间的距离, 两个节点之间的跨度越大,相距就越远. 指向null的所有前进指针的跨度都为0, 因为他们没有连向任何节点

后退指针 (backward)用于从表尾向表头方向访问节点, 跟一次可以跳多个节点的前进指针不同, 每个节点只有一个后退指针、只能后退至前一个节点.

分值和成员 (score)是一个double类型的浮点数, 跳跃表中所有的节点都按分值从小到大来排序(obj)是一个指针、指向一个字符串对象、字符串对象中则保存着一个SDS

在同一个跳表中、各节点保存的成员对象必须是唯一的、但多个节点保存的分值却是可以相同的. 分值相同的节点按照成员对象在字典序中的大小排序, 小的节点放在前边.

跳跃表

仅靠多个跳跃表节点就可以完成一个跳表、但使用一个zskiplist结构来持有这些节点, 程序可以更方便的对整个跳表进行处理, eg. 快速访问跳表的头结点和尾结点、快速获取节点数量.

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

headertail 指针分别指向表头和表尾节点, 程序定位表头节点和表尾节点的复杂度是 O(1)

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

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

重点回顾

  1. 跳表是有序集合的底层实现之一
  2. Redis的跳表实现由 zskiplistzskiplistNode 两个结构组成, 其中 zskiplist 用于保存跳表信息(eg. 表头节点、表尾节点、长度)、而zskiplistNode 则用于表示跳跃表节点
  3. 每个跳跃表节点的层高都是 1~32 之间的随机数
  4. 同一个跳表中、多个节点可以包含相同的分值、但每个节点的成员对象必须是唯一的
  5. 跳表中的节点按照分值大小排序、分值相同时、节点按照成员对象的大小排序

你可能感兴趣的:(Redis数据结构学习-跳表(四))