Redis跳表
Redis中的跳表实现在3.0版本是在Redis.h中,到3.2版本时定义到了server.h中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30/* ZSETs use a specialized version of Skiplists */
/*
* 跳跃表节点
*/
typedef struct zskiplistNode {
// 成员对象
robj *obj;
// 分值
double score;
// 后退指针
struct zskiplistNode *backward;
// 层
struct zskiplistLevel {
// 前进指针
struct zskiplistNode *forward;
// 跨度
unsigned int span;
} level[];
} zskiplistNode;
/*
* 跳跃表
*/
typedef struct zskiplist {
// 表头节点和表尾节点
struct zskiplistNode *header, *tail;
// 表中节点的数量,除去第一个节点
unsigned long length;
// 表中层数最大的节点的层数,除去第一个节点
int level;
} zskiplist;
幂次定律
在Redis中,跳表返回的随机层数值使用的算法就是幂次定律。
- 含义是:如果某件事的发生频率和它的某个属性成幂关系,那么这个频率就可以称之为符合幂次定律。
- 表现是:少数几个事件的发生频率占了整个发生频率的大部分,而其余的大多数事件只占整个发生频率的一个小部分。
1
2
3
4
5
6int zslRandomLevel(void) {
int level = 1;
while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))//ZSKIPLIST_P(0.25)所以level+1的概率为0.25
level += 1;
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
创建跳跃表
1 | /* |
插入节点
1 | /* |
删除节点
1 | /* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank |
获取节点排名
1 | /* Find the rank for an element by both score and key. |
区间操作
1 | zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec *range) { //返回第一个分数在range范围内的节点 |
为什么Redis要使用跳表来实现有序集合而不是红黑树?
Redis中有序集合支持的核心操作主要有:
- 插入一个数据
- 删除一个数据
- 查找一个数据
- 按照区间查找数据
- 迭代输出有序序列
其中插入、删除、查找以及迭代有序序列这几个操作,红黑树也能够完成,时间复杂度跟跳表是一样的。
但是按照区间查找数据这个操作,红黑树的效率没有跳表高。跳表可以在O(logn)时间复杂度定位区间的起点,然后在原始链表中顺序先后查询,这非常高效。
此外,相比于红黑树,跳表还具有代码更容易实现、可读性高、不容易出错、更加灵活等特点。