- Redis版本:5.0.5
- 文件: t_zset.c server.h
张老师讲解redis中跳跃表的概念
跳跃表的实现C++
本篇文章则针对源码来进行分析
跳跃表(zskiplist zsiplistnode)概念
- 跳跃表是一种有序数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问的目的。
- 跳跃表平均支持O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批处理节点。
Redis何时使用跳跃表? - Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现。
跳跃表的结构
typedef struct zskiplistNode {
sds ele; //sds字符串
double score; //分值
struct zskiplistNode *backward; //后退指针,只在第1层有
//level
struct zskiplistLevel {
struct zskiplistNode *forward; //前向指针
unsigned long span; //跨度
} level[]; //柔性数组
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail; //跳表的头指针和尾指针
unsigned long length; //元素总数
int level; //层级
} zskiplist;
解析创建插入查找删除函数(在代码中用注释详细解析)
1.创建函数
zskiplistNode *zslCreateNode(int level, double score, sds ele) {
//分配内存
zskiplistNode *zn =
zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
zn->score = score;
zn->ele = ele;
return zn;
}
/* Create a new skiplist. */
zskiplist *zslCreate(void) {
int j;
zskiplist *zsl;
zsl = zmalloc(sizeof(*zsl));
zsl->level = 1;
zsl->length = 0;
zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
//初始化每一层节点
for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
zsl->header->level[j].forward = NULL;
zsl->header->level[j].span = 0;
}
zsl->header->backward = NULL;
zsl->tail = NULL;
return zsl;
}
2.插入函数(在代码中讲解的很详细)
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
//update 用来保存每一层需要插入节点位置的前一个节点
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level;
serverAssert(!isnan(score));
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
/* store rank that is crossed to reach the insert position */
rank[i] = i == (zsl->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)))
{
//累加当前level到插入节点位置的span
rank[i] += x->level[i].span;
x = x->level[i].forward;
}
//保存每一层的要插入位置的前一个节点
update[i] = x;
}
//随机level
level = zslRandomLevel();
//新插入节点的level大于当前跳表的level
if (level > zsl->level) {
for (i = zsl->level; i < level; i++) {
rank[i] = 0;
//多出的level的前一个节点为header,跨度就是当前zskiplist的长度
update[i] = zsl->header;
update[i]->level[i].span = zsl->length;
}
//更新跳表的level
zsl->level = level;
}
//创建一个新zskiplistnode
x = zslCreateNode(level,score,ele);
//将其插入在每一层中
for (i = 0; i < level; i++) {
x->level[i].forward = update[i]->level[i].forward;
update[i]->level[i].forward = x;
/* update span covered by update[i] as x is inserted here */
//rank[0] - rank[i] 就是新插入节点和每一层前一个节点之间的span
x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]); //新插入节点的span
update[i]->level[i].span = (rank[0] - rank[i]) + 1; //更新插入位置前一个节点的span
}
/* increment span for untouched levels */
for (i = level; i < zsl->level; i++) {
update[i]->level[i].span++;
}
//为新插入节点的前驱指针赋值
x->backward = (update[0] == zsl->header) ? NULL : update[0];
//没有插在第1层的结尾则将其下一个节点的前向指针指向x
if (x->level[0].forward)
x->level[0].forward->backward = x;
//否则更新zskiplist的tail指针
else
zsl->tail = x;
//长度+1
zsl->length++;
return x;
}
3.删除函数(zslDelet函数是暴露给用户可见的)
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) //update为要删除节点的前一个节点的集合
{
int i;
//从第一层向上删除
for (i = 0; i < zsl->level; i++) {
//如果当前节点和要删除节点的值相同则删除
if (update[i]->level[i].forward == x) {
update[i]->level[i].span += x->level[i].span - 1;
update[i]->level[i].forward = x->level[i].forward;
}
//否则将该结点的span-1
else {
update[i]->level[i].span -= 1;
}
}
//该结点不是尾节点
if (x->level[0].forward) {
x->level[0].forward->backward = x->backward;
}
//是尾节点则更新尾节点
else {
zsl->tail = x->backward;
}
//将删除节点后的空层去除并且level--
while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
zsl->level--;
//元素总数-1
zsl->length--;
}
//删除
int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
int i;
x = zsl->header;
//和插入节点时的作用一样,找到删除节点位置的前一个节点
for (i = zsl->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;
}
/* We may have multiple elements with the same score, what we need
* is to find the element with both the right score and object. */
x = x->level[0].forward;
//判断x和要删除的节点是否相同
if (x && score == x->score && sdscmp(x->ele,ele) == 0) {
zslDeleteNode(zsl, x, update);
if (!node)
zslFreeNode(x);
else
*node = x;
return 1;
}
return 0; /* not found */
}
4释放函数这个函数的源代码比较简单,就不进行解析了。
void zslFreeNode(zskiplistNode *node) {
sdsfree(node->ele);
zfree(node);
}
/* Free a whole skiplist. */
void zslFree(zskiplist *zsl) {
zskiplistNode *node = zsl->header->level[0].forward, *next;
zfree(zsl->header);
while(node) {
next = node->level[0].forward;
zslFreeNode(node);
node = next;
}
zfree(zsl);
}
其实zskiplist在Redis中的主要用处是和dict一起实现Sorted Set。
函数 | 作用 | 时间复杂度 |
---|---|---|
zslCreate | 创建一个新的跳跃表 | O(1) |
zslFree | 释放给定跳跃表,以及表中包含的所有结点 | O(N) |
zslInsert | 将包含成员和分值的新节点添加到跳跃表中 | 平均为O(logN),最坏为O(N) |
zslDelete | 删除跳跃表中包含给定成员和分值 | 平均为O(logN),最坏为O(N) |