Redis 底层数据结构之跳跃表

时间:2021-02-26 07:04:39

文章参考

《Redis 设计与实现》黄建宏

Redis(2) 跳跃表

跳跃表

跳跃表 skiplist 是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的,平均复杂度 O(logN)、最坏 O(N)

Redis 使用跳表作为有序集合集合键的底层实现之一,如果一个有序集合包含的元素数量比较多, 或者有序集合中成员是比较长的字符串时,Redis 就会使用跳表来作为有序集合键的底层实现。

另一个使用跳表的事集群节点中用作内部数据结构。

Redis 底层数据结构之跳跃表

  • header 指向跳跃表的表头节点
  • tail 指向跳跃表的表尾节点
  • level 记录目前跳跃表内,层数最大的那个节点的层数(表头节点不计算在内)
  • length 记录跳跃表的长度,也就是目前节点的数量

跳跃表节点 zskiplistNode

typedef struct zskiplistNode {
// 层
struct zskiplistLevel {
// 前进指针
struct zskiplistNode *forward;
unsigned int span;
} level[];
// 后退指针
struct zskiplistNode *backward;
// 分值
double score;
// 成员对象
robj *obj;
} zskiplistNode;
  1. 跳跃表节点的 level 数组可以包含多个元素, 每个元素都包含一个指向其他节点的指针

    每次创建一个新的跳跃表节点时候,随机生成 1~32 之间的值作为 level 数组的大小,即层的高度

  2. 前进指针

    每个层都有一个指向表尾方向的前进指针 forward,用于遍历到结尾

  3. 跨度

    span属性用于记录两个节点之间的距离:两个节点跨度越大,它们相距得越远,

  4. 后退指针

    后退指针用于从表尾向表头访问节点(和 forward 不同,每次只能后退一个节点)

  5. 分值和成员

    节点都按分值从小到大排序

    节点的成员对象 obj 指向一个 SDS 字符串

跳跃表

多个跳跃表节点可以组成一个跳跃表

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

header 和 tail 指针能让定位 表头和表尾 复杂度为 O(1)

length 让程序可以在 O(1) 复杂度返回跳跃表长度。

跳跃表的查找过程

比如我们想要查找 7,查找的路径则是沿着下图中标注出红色指针所指的方向进行的(每次查找都是从最高的 level 开始):

Redis 底层数据结构之跳跃表

跳跃表的创建过程

Redis 底层数据结构之跳跃表

从上面的创建和插入的过程中可以看出,每一个节点的层数(level)是随机出来的,而且新插入一个节点并不会影响到其他节点的层数,因此,插入操作只需要修改节点前后的指针,而不需要对多个节点都进行调整,这就降低了插入操作的复杂度。