需要一些C语言基础,本人也只是可以看懂C语言的水平,这样即可
阅读该文需要对Redis跳表有一定的认知。
跳表源码
skiplist和平衡树、哈希表的比较
-
哈希表
哈希表只能做单个key的查找,不适合范围查找。
-
平衡树
在做范围查找时,需要找到树上指定范围的最小数。然后进行中序遍历,查找其他不超过范围最大值的节点。而跳表,只需要找到较小值后,在第一层的链表向后遍历指定个数,就可以找到所有数值。
平衡树的插入,删除操作引起平衡树的调整,逻辑复杂,跳表只需要改变前后节点。
从内存占用上来讲跳表的一个节点的平均指针数目为1.33,而平衡树为2。
算法实现上,跳表也要更简单一些。
相比而言,跳表要更简单高效,占用空间少。
跳跃表如何进行操作
跳跃表的插入过程:
跳跃表的查找过程:
可以看出他的很关键的一个因素:层数。它会从高层开始查找,查找不到,进行下沉,直到找到。
跳跃表的实现
跳跃表节点:
typedef struct zskiplistNode {
// value
sds ele;
// 分值
double score;
// 后退指针
struct zskiplistNode *backward;
// 层
struct zskiplistLevel {
// 前进指针
struct zskiplistNode *forward;
// 跨度
unsigned long span;
} level[];
} zskiplistNode;
名词理解
7在第三层的跨度即为4,前进指针指向37.后退指针指向3(注意前向指针每一层都有,后退指针是每一节点只有一个)。
跳跃表:
typedef struct zskiplist {
// 跳跃表头指针
struct zskiplistNode *header, *tail;
// 表中节点的数量
unsigned long length;
// 表中层数最大的节点的层数
int level;
} zskiplist
随机层数(理解跳表重要的一个环节):
randomLevel()
level := 1
// random()返回一个[0...1)的随机数
while random() < p and level < MaxLevel do
level := level + 1
return level
p默认为0.25,MaxLevel为32。
这是一个伪代码,可以发现,生成的随机数每次小于P,层数就加一。
节点层数恰好等于1的概率为1-p。
节点层数大于等于2的概率为p,而节点层数恰好等于2的概率为p(1-p)。
节点层数大于等于3的概率为p ^ 2,而节点层数恰好等于3的概率为p ^ 2(1-p)。
节点层数大于等于4的概率为p ^ 3,而节点层数恰好等于4的概率为p ^ 3(1-p)。
…
层数,使我们可以做到类似在对数组用到的二分查找法。
创建跳跃表
zskiplist *zslCreate(void) {
int j;
zskiplist *zsl;
zsl = zmalloc(sizeof(*zsl));
// 初始化层数为 1
zsl->level = 1;
// 初始化长度为 0
zsl->length = 0;
// 创建一个层数为 32,分数为 0,没有 value 值的跳跃表头节点
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;
}
插入节点
插入节点的关键在找到何处插入该节点,跳跃表是按照score分值进行排序的。
讲解源码前,先知道大概原理:
查找步骤:从当前zskiplist保存的最高的level的头节点开始,向前查找,如果当前节点的score小于插入节点的score,继续向前;如果大于,或者则降低一层继续查找,直到第一层为止。此时,插入点就位于找到的节点之后。
在这个过程中会记录在每层要插入节点位置的前一个节点,因为要更新前一节点的跨度,和其前向指针。(update数组)
也会记录每层插入节点的前一节点的总排名,用于更新跨度(跨度不断相加)(rank数组)
所有查找过程,先查找分数,如分数相同,再找对应的value。
这里理解了update和rank后,代码也就很容易理解了,注释很详细。
跳表有点类似二分查找的特性
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {
// updata[]数组记录每一层位于插入节点的前一个节点
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
// rank[]记录每一层位于插入节点的前一个节点的排名
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level;
serverAssert(!isnan(score));
x = zsl->header; // 表头节点
// 从最高层开始查找
for (i = zsl->level-1; i >= 0; i--) {
// 存储rank值是为了计算重新构建后的span值
rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
// 前向指针不为空,前置指针的分值小于score或等于当前指针的分值但不是所找obj情况下,继续向前查找
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score &&
compareStringObjects(x->level[i].forward->obj,obj) < 0))) {
//这里是求每一层插入节点的前一节点总跨度,不断进行跨度相加
rank[i] += x->level[i].span;
x = x->level[i].forward;
}
// 存储当前层上位于插入节点的前一个节点
update[i] = x;
}
// 此处假设插入节点的成员对象不存在于当前跳跃表内,即不存在重复的节点
// 随机生成一个level值
level = zslRandomLevel();
if (level > zsl->level) {
// 如果level大于当前存储的最大level值
// 设定rank数组中大于原level层以上的值为0
// 同时设定update数组大于原level层以上的数据
for (i = zsl->level; i < level; i++) {
rank[i] = 0;
update[i] = zsl->header;
//不明白此处为何更新为length的长度
update[i]->level[i].span = zsl->length;
}
// 更新level值
zsl->level = level;
}
// 创建插入节点
x = zslCreateNode(level,score,obj);
for (i = 0; i < level; i++) {
// 针对跳跃表的每一层,改变其forward指针的指向
x->level[i].forward = update[i]->level[i].forward;
update[i]->level[i].forward = x;
// 更新插入节点的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值
for (i = level; i < zsl->level; i++) {
update[i]->level[i].span++;
}
// 设定插入节点的backward指针
x->backward = (update[0] == zsl->header) ? NULL : update[0];
if (x->level[0].forward)
x->level[0].forward->backward = x;
else
zsl->tail = x;
// 跳跃表长度+1
zsl->length++;
return x;
}
删除节点
理解插入后,删除节点就很好理解了
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **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;
} else {
//减一,即是不存在该层,因为这个节点将会被删除
update[i]->level[i].span -= 1;
}
}
//考虑是否为尾指针
if (x->level[0].forward) {
//修改后退指针
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--;
}
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 &&
equalStringObjects(x->obj,obj) < 0)))
{
x = x->level[i].forward;
}
update[i] = x;
}
//如果分值等于且就是所要找的obj,就进行删除。
x = x->level[0].forward;
if (x && score == x->score && equalStringObjects(x->obj,obj)) {
zslDeleteNode(zsl, x, update);
if (!node)
zslFreeNode(x);
else
*node = x;
return 1;
}
return 0; /* not found */
}