跳跃列表(skiplist)是一种有序的数据结构,它通过在每个节点维护多个指向其他节点的指针,我们可以平均O(logN) 的时间复杂度快速访问节点,大部分情况下它的性能可以和平衡树相媲美,不过实现起来更为简单。
跳跃列表是 Redis 中有序集合的一种实现方式,在有序集合的元素数量较多或者是元素成员长度较长时,Redis 就会使用跳跃列表加字典来存储元素。
大家都知道 zset 每一个 value 都有一个 score ,zset 根据每个 value 的 score 将其排序,并且元素不会重复,那么,为了能通过每个元素的 value 快速得到对应的 score ,zset 的存储结构里就需要字典来做这么一个映射。
而跳跃列表,用于通过score 快速地定位 value。
跳跃列表实现
Redis 中的跳跃列表由 zskiplistNode 结构(表示跳跃列表节点)和 zskiplist(用于保存跳跃列表相关信息)构成。
zskiplistNode
typedef struct zskiplistNode {
// 层
struct zskiplistLevel {
// 前进指针
struct zskiplistNode *forward;
// 跨度
unsigned int span;
} level[];
// 后退指针
struct zskiplistNode *backward;
// 分值
double score;
// 成员对象
robj *obj;
} zskiplistNode;
zskiplist
typedef struct zskiplist {
// 表头节点和表尾节点
struct zskiplistNode *header,*tail;
// 表中节点的数量
unsigned long length;
// 表中的最大层数
int level;
}zskiplist;
下面展示了一个跳跃列表
Redis 的跳跃列表共有 64 层,意味着最多可以容纳 2^64 次方个元素。
最左边的是一个zskiplist 结构:
header 指针和 tail 指针分别指向跳跃列表表头和表尾节点,length 记录节点的数量,这里为3(表头节点不算),level 记录表中最后的层数,这里为4(表头节点不算)
右边四个是 zskiplistNode 结构:
每个层都有两个属性,前进指针和跨度,前进指针用于访问位于表尾方向和该层层数相同的节点(没有则指向NULL),跨度记录了前进指针指向节点和当前节点的距离,也就是表示了中间经过了多少个节点,图上箭头中的数字表示跨度。
后退指针(图中的BW)指向当前节点的前一个节点,用于反向遍历。
分值:跳跃表中节点按分值从小到大排序,图中三个节点分值为3.0,4.0,5.0
成员对象:即和分值所对应的value,本质上是一个SDS对象
随机层数
跳跃列表节点的 level 数组是可以包含多个元素的,每个元素都包含一个指向其他节点的指针,而这个层数是在创建一个节点时,Redis 根据幂次定律随机生成的。
最后,再说明一点,zset 虽然使用了字典加跳跃列表两种数据结构来保存数据,但是实际上字典和跳跃列表是共享 value 和 score 的,即由指针指向了同一个对象。