跳表(Skip List)是一种高效的动态数据结构,在Redis中用于实现有序集合(ZSET),支持快速的范围查询和插入删除操作。相比传统平衡树(如AVL或红黑树),跳表的实现更简单且性能优异。本篇将深入剖析Redis跳表的源码实现,包括结构定义、插入删除逻辑和随机层高生成。
- 用途:ZSET的核心数据结构,存储元素和分数(score),支持按分数排序。
- 特性:结合链表的简单性和二分查找的高效性,平均时间复杂度为O(log n)。
跳表的实现位于src/server.h
和src/t_zset.c
中。以下是核心结构:
代码片段(server.h
):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| typedef struct zskiplistNode {
sds ele; // 元素(SDS字符串)
double score; // 分数
struct zskiplistNode *backward; // 后退指针
struct zskiplistLevel {
struct zskiplistNode *forward; // 前进指针
unsigned long span; // 跨度(到下个节点的距离)
} level[]; // 层数组(柔性数组)
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header; // 头节点
struct zskiplistNode *tail; // 尾节点
unsigned long length; // 节点数
int level; // 最大层数
} zskiplist;
|
硬核解析:
zskiplistNode
:每个节点包含元素、分数和多层前进指针(level[]
)。level[i].forward
:指向该层下一个节点。span
:记录跨越的节点数,用于范围查询。backward
:后退指针,便于双向遍历。zskiplist
:管理整个跳表,header
不存储数据,仅作为起点。
Mermaid结构图:
classDiagram
class zskiplist {
-header: zskiplistNode*
-tail: zskiplistNode*
-length: ulong
-level: int
}
class zskiplistNode {
-ele: sds
-score: double
-backward: zskiplistNode*
-level[]: zskiplistLevel
}
class zskiplistLevel {
-forward: zskiplistNode*
-span: ulong
}
zskiplist o--> "1" zskiplistNode : "header"
zskiplistNode o--> "n" zskiplistLevel : "level[]"
zskiplistNode o--> "1" zskiplistNode : "backward"
zskiplistLevel o--> "1" zskiplistNode : "forward"
代码片段(t_zset.c
):
1
2
3
4
5
6
7
8
9
| #define ZSKIPLIST_MAXLEVEL 32
#define ZSKIPLIST_P 0.25 // 随机因子
int zslRandomLevel(void) {
int level = 1;
while ((random() & 0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
level += 1;
return (level < ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
|
硬核解析:
- 随机性:基于概率
P=0.25
,每层概率递减(1/4, 1/16...)。 - 上限:最大层数32,避免过高开销。
- 意义:层高决定查找效率,平均O(log n)。
代码片段(t_zset.c
):
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
31
32
33
34
35
36
| zskiplistNode *zslInsert(zskiplist *zl, double score, sds ele) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level = zslRandomLevel();
x = zl->header;
for (i = zl->level - 1; i >= 0; i--) {
rank[i] = i == (zl->level - 1) ? 0 : rank[i + 1];
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score && sdscmp(x->level[i].forward->ele, ele) < 0))) {
rank[i] += x->level[i].span;
x = x->level[i].forward;
}
update[i] = x;
}
zskiplistNode *node = zslCreateNode(level, score, ele);
for (i = 0; i < level; i++) {
node->level[i].forward = update[i]->level[i].forward;
update[i]->level[i].forward = node;
node->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
update[i]->level[i].span = (rank[0] - rank[i]) + 1;
}
for (i = level; i < zl->level; i++) {
update[i]->level[i].span++;
}
node->backward = (update[0] == zl->header) ? NULL : update[0];
if (node->level[0].forward)
node->level[0].forward->backward = node;
else
zl->tail = node;
zl->length++;
if (level > zl->level) zl->level = level;
return node;
}
|
硬核解析:
- 查找路径:从最高层向下,记录每层的前驱节点(
update[]
)和累计跨度(rank[]
)。 - 插入逻辑:更新前驱的
forward
和span
,设置新节点的backward
。 - 跨度调整:确保范围查询的正确性。
Mermaid插入流程:
graph TD
A["zslInsert()"] --> B["zslRandomLevel()"]
B --> C["创建新节点"]
C --> D["从顶层查找插入位置"]
D --> E{"找到前驱节点?"}
E -->|Yes| F["更新forward和span"]
F --> G["设置backward"]
G --> H["更新length和level"]
H --> I["返回节点"]
代码片段(t_zset.c
):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| int zslDelete(zskiplist *zl, double score, sds ele, zskiplistNode **node) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
int i;
x = zl->header;
for (i = zl->level - 1; i >= 0; i--) {
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score && sdscmp(x->level[i].forward->ele, ele) < 0)))
x = x->level[i].forward;
update[i] = x;
}
x = x->level[0].forward;
if (x && score == x->score && sdscmp(x->ele, ele) == 0) {
zslDeleteNode(zl, x, update);
if (node) *node = x; else zslFreeNode(x);
return 1;
}
return 0;
}
|
硬核解析:
- 查找:类似插入,找到目标节点。
- 删除:调整前驱的
forward
和span
,更新backward
和tail
。
- 时间复杂度:插入、删除、查找平均O(log n),最坏O(n)。
- 空间复杂度:O(n),每节点平均层数约为1/(1-P) ≈ 1.33。
- 优化:随机层高避免确定性失衡。
- 收获:掌握跳表的实现及其在ZSET中的应用。
- 调试技巧:用
gdb
打印跳表层结构(如p zl->header->level[0].forward
)。 - 下一步:探索事件驱动模型。