Redis源码学习基本数据结构之skiplist
2019-04-29 本文已影响0人
lixin_karl
介绍
跳跃表(skiplist)是一种随机化的数据,由 William Pugh 在论文《Skip lists: a probabilistic alternative to balanced trees》中提出,这种数据结构以有序的方式在层次化的链表中保存元
素,它的效率可以和平衡树媲美——查找、删除、添加等操作都可以在对数期望时间下完成,并且比起平衡树来说,跳跃表的实现要简单直观得多。
skiplist结构
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, *tail;//头 尾
unsigned long length;//节点个数
int level;//层数最大的那个节点的层数
} zskiplist;
typedef struct {
double min, max;
int minex, maxex; /*minex=1就是不包括min即左边开区间 maxex同理*/
} zrangespec;
主要api及代码
zskiplist *zslCreate(void);//创建跳跃表
void zslFree(zskiplist *zsl);//释放跳跃表内存
//插入数据
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele);
//删除数据
int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node);
//找到第一个在range范围内的节点
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec *range);
//找到最后一个在range范围内的节点
zskiplistNode *zslLastInRange(zskiplist *zsl, zrangespec *range);
zslInsert 插入数据
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level;
serverAssert(!isnan(score));//score必须是数值
x = zsl->header; //头结点
for (i = zsl->level-1; i >= 0; i--) { //从高层到底层逐渐递进
//如果i==zsl->level-1 rank[i] = 0 否则等于高一层的rank
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 &&
//但是ele小于
sdscmp(x->level[i].forward->ele,ele) < 0)))
{
//更新update对应节点的排名
rank[i] += x->level[i].span;
x = x->level[i].forward; //更新x
}
//找到第i层的最后一个小于score的节点
update[i] = x;
}
level = zslRandomLevel();// <=64 随机生成 层数
// 如果随机生成的层数大于 这个跳跃表的层数
if (level > zsl->level) {
for (i = zsl->level; i < level; i++) {//更新
rank[i] = 0;
//新增加的高层必然指向head
update[i] = zsl->header;
//直接到跳跃表的tail
update[i]->level[i].span = zsl->length;
}
zsl->level = level;
}
x = zslCreateNode(level,score,ele);//创建一个跳跃表节点
//更新update节点相关数据 以及 x的各层数据
for (i = 0; i < level; i++) {
x->level[i].forward = update[i]->level[i].forward;
update[i]->level[i].forward = x;//插入数据
//插入节点后 第i层x前后跨度分别为
//(rank[0] - rank[i]) + 1、update[i]->level[i].span - (rank[0] - rank[i])
x->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 < zsl->level; i++) {
update[i]->level[i].span++;//各层跨度增加1
}
//update[0]应该是它前一个数据
x->backward = (update[0] == zsl->header) ? NULL : update[0];
if (x->level[0].forward)
//更新x后一个数据的backforward
x->level[0].forward->backward = x;
else //如果x的紧跟着的下一个节点为空说明x是最后一个及节点
zsl->tail = x;
zsl->length++; //更新节点个数
return x;
}
zslDelete 删除节点
//删除跳跃链表节点 如果此节点被删除返回1 其他返回0
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 &&
//下一个数据的分数小于现在的分数 或者 分数相等 ele小
(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;//找到下一个节点
}
//把每一层所指向的下一个及节点第一个大于等于 score ele的节点地址都记录下来
update[i] = x;
}
//如果存在要被删除的数据 x的下一个数据必然就是要被删除的数据了
x = x->level[0].forward;
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 */
}
// zsl 跳跃表 x 将要被删除的节点 update删除x需要更新节点信息的节点列表
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
int i;
for (i = 0; i < zsl->level; i++) {
//如果此节点i层下一个指向x 更新
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;
} else {//否则span-1即可
update[i]->level[i].span -= 1;
}
}
if (x->level[0].forward) {//看x是不是最后一个节点
x->level[0].forward->backward = x->backward;
} else {
zsl->tail = x->backward;
}
//更新跳跃表的最高的层数
while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
zsl->level--;
zsl->length--;//节点个数必然要减一
}
Redis中的作用
跳跃表在 Redis 的唯一作用,就是实现有序集数据类型。跳跃表将指向有序集的 score 值和 member 域的指针作为元素,并以 score 值为索引,对有序集元素进行排序。