zskiplist(跳跃表)
原理
zskiplist是一个有序的集合,为了解决其他链表的插入、删除效率低、查询元素需要循环遍历等缺点,redis就采用了一个特殊的数据结构zskiplist(跳跃表)。
它在性能上跟红黑树差不多, 同时又比红黑树的实现简单。在插入、删除、查询等操作上的时间复杂度为o(logn)。
分析
- 它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的;
- Redis 只在两个地方用到了跳跃表,一个是实现有序集合健,另一个是在集群节点中用做内部数据结构,除此之外,跳跃表在 Redis 里没有其它用途;
- 一个常规的链表在做元素查找时,只能从头到尾的遍历来得到元素。时间复杂度为 O(n);
- zskiplist的头结点不是一个有效的节点,它有ZSKIPLIST_MAXLEVEL层(32层),每层的forward指向该层跳跃表的第一个节点,若没有则为null;
- redis限定了ZSKIPLIST_P抛硬币正面的概率为1/4
zskiplist 图解分析

zskiplist如何查找元素分析
加入我们想查找 9 这个元素,步骤如下:
- 先遍历最高层第四层找到16;
- 9<16,找到第三层13;
- 9<13,找到二层8;
- 9>8,找到8的下一个节点9。

zskiplist插入元素分析
创建一个zskiplist的时间复杂度为O(1);
- 找到合适的位置插入
- 调用zslRandomLevel 得到一个插入节点的层数,有 1/4 概率加入上一层;
- 调用zslCreateNode创建一个新的节点;
- 修改插入位置的前后节点以及插入节点本身的
backward、forward、span等属性

zskiplist删除元素分析
释放一个节点的内存 时间复杂度O(1);
释放整个skiplist的内存 时间复杂度O(n);
从skiplist中删除并释放掉一个节点 时间复杂度O(logn),会涉及到3个步骤:
- 根据
ele和score找到节点的位置(代码里变量x即为该节点,update记录每层x的上一个节点) - 调动
zslDeleteNode把x节点从skiplist逻辑上删除 - 释放x节点内存
根据范围删除节点 时间复杂度O(log(n)+m), m是范围内元素的个数;
zskiplist跟红黑树对比
优点:
- 比红黑树占用更多的内存,每个节点的大小取决于该节点的层数;
- 空间局部性较差导致缓存命中率低,感觉上会比红黑树更慢;
优点:
- 实现比红黑树简单;
- 比红黑树更容易扩展,作者之后实现zrank指令时没怎么改动代码;
- 红黑树插入删除时为了平衡高度需要旋转附近节点,高并发时需要锁。skiplist不需要考虑;
- 一般用zset的操作都是执行zrange之类的操作,取出一片连续的节点。这些操作的缓存命中率不会比红黑树低。
源码
在 redis/src/server.h 中
















