结构体定义
结构体定义在server.h文件内
跳表节点数据结构
typedef struct zskiplistNode {
sds ele; /* 使用 SDS(Simple Dynamic Strings)存储的元素值 */
double score; /* 用于排序的元素分数 */
struct zskiplistNode *backward; /* 后向指针,指向前一个节点 */
//节点的level数组,保存每层上的前向指针和跨度
struct zskiplistLevel {
struct zskiplistNode *forward; /* 前向指针 */
unsigned long span; /* 到同一层级的下一个节点的跨度或距离 */
} level[];
} zskiplistNode;
跳表结构体
typedef struct zskiplist {
struct zskiplistNode *header, *tail; //跳表头尾节点
unsigned long length; //跳表长度
int level; //跳表最大层数
} zskiplist;
zset由dict和zskiplist组成
typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;
跳表节点查询过程
查找一个跳表节点的过程时,跳表会从头节点的最高层开始,逐一遍历每一层。在遍历某一层的跳表节点时,会用跳表节点中的 SDS 类型的元素和元素的权重来进行判断,共有两个判断条件:
- 如果当前节点的权重「小于」要查找的权重时,跳表就会访问该层上的下一个节点。
- 如果当前节点的权重「等于」要查找的权重时,并且当前节点的 SDS 类型数据「小于」要查找的数据时,跳表就会访问该层上的下一个节点。当score相同时,会根据member排序,例如"apple"应该排在"banana"前面。
如果上面两个条件都不满足,或者下一个节点为空时,跳表就会使用目前遍历到的节点的 level 数组里的下一层指针,然后沿着下一层指针继续查找,这就相当于跳到了下一层接着查找。
如果要查找「元素:abcd,权重:4」的节点,查找的过程是这样的:
- 先从头节点的最高层开始,L2 指向了「元素:abc,权重:3」节点,这个节点的权重比要查找节点的小,所以要访问该层上的下一个节点;
- 但是该层的下一个节点是空节点( leve[2]指向的是空节点),于是就会跳到「元素:abc,权重:3」节点的下一层去找,也就是 leve[1];
- 「元素:abc,权重:3」节点的 leve[1] 的下一个指针指向了「元素:abcde,权重:4」的节点,然后将其和要查找的节点比较。虽然「元素:abcde,权重:4」的节点的权重和要查找的权重相同,但是当前节点的 SDS 类型数据「大于」要查找的数据,所以会继续跳到「元素:abc,权重:3」节点的下一层去找,也就是 leve[0];
- 「元素:abc,权重:3」节点的 leve[0] 的下一个指针指向了「元素:abcd,权重:4」的节点,该节点正是要查找的节点,查询结束。
创建跳表
/* 创建一个新的跳表。*/
zskiplist *zslCreate(void) {
int j;
zskiplist *zsl;
// 分配内存以存储跳表结构
zsl = zmalloc(sizeof(*zsl));
// 初始化跳表的基本属性
zsl->level = 1;
zsl->length = 0;
// 创建头结点并初始化 ZXKIPLIST_MAXLEVEL定义的是最高层数,我的redis版本定义为32
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;
}
#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^64 elements */
zskiplistNode *zslCreateNode(int level, double score, sds ele) {
// 分配内存以存储跳表节点结构及其层级信息
zskiplistNode *zn = zmalloc(sizeof(*zn) + level * sizeof(struct zskiplistLevel));
// 初始化节点的分数和元素值,score = 0、ele = null
zn->score = score;
zn->ele = ele;
//返回该节点作为头节点
return zn;
}
插入节点
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele);
unsigned char *zzlInsert(unsigned char *zl, sds ele, double score);
这里有两个方法,第二个方法是向压缩列表中插入,我们看第一个跳表实现的。
/* Insert a new node in the skiplist. Assumes the element does not already
* exist (up to the caller to enforce that). The skiplist takes ownership
* of the passed SDS string 'ele'. */
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level;
// 检查分数是否为 NaN(不是数字)
serverAssert(!isnan(score));
// 从头节点开始向下遍历每一层,寻找插入位置
x = zsl->header;
// level - 1,即从最高层开始向下
for (i = zsl->level-1; i >= 0; i--) {
/* 存储到达插入位置时经过的排名 */
// 如果是最高层,rank[31] = 0
rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
//forward指向同一层级下一个节点
//下一个节点不为空 且 下一个节点的分数小于传入新元素分数 或 分数相同但新元素sds大于下一个节点sds
//这种情况应该继续向后
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;
}
//将i层找到的前驱节点x记录
//目的是将新插入的节点 `x` 记录到 `update` 数组中,以便后续更新跳表的结构。
update[i] = x;
//i层查找结束,继续从x节点的下一层开始往后寻找
}
// 这是官方注释翻译,不产生相同元素由程序员保证
/* 假设元素不存在,因为我们允许分数重复,重新插入相同元素应该不会发生,
* 因为 zslInsert() 的调用者应该在哈希表中检查元素是否已存在。*/
// 随机生成节点的层级
// 内部实现:0 - 1范围内生成一个随机数,如果这个随机数小于 0.25(相当于概率 25%)
//那么层数就增加 1 层,然后继续生成下一个随机数,
//直到随机数的结果大于 0.25 结束,最终确定该节点的层数
level = zslRandomLevel();
// 如果生成的层级大于跳表的最大层级
// 超出部分的层数将会被使用,将其记录到update供后续使用
if (level > zsl->level) {
for (i = zsl->level; i < level; i++) {
rank[i] = 0;
update[i] = zsl->header;
update[i]->level[i].span = zsl->length;
}
//最大层级更新
zsl->level = level;
}
// 创建新节点
x = zslCreateNode(level, score, ele);
// 将新节点插入跳表中
// 从第0层到第level层都需要插入
for (i = 0; i < level; i++) {
x->level[i].forward = update[i]->level[i].forward;
//将 `update[i]->level[i].forward` 更新为新节点 `x`,以确保跳表中节点的正确连接关系。
update[i]->level[i].forward = x;
// 更新跨度
x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
update[i]->level[i].span = (rank[0] - rank[i]) + 1;
}
/* increment span for untouched levels */
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;
// 增加跳表长度
zsl->length++;
// 返回新插入的节点
return x;
}