Redis SkipList
数据结构
zskipListNode
/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode
{
// sorted set中的元素
sds ele;
// 权重, 跳表中所有的元素都是按照score进行排列的
double score;
// 后向指针(为了从后往前遍历方便)
struct zskiplistNode *backward;
// level:每一层都有一个当前节点再本层的跨度span和前向指针
struct zskiplistLevel
{
struct zskiplistNode *forward;
// 这个跨度指的是当前节点的forward指针和自己之间的距离
unsigned long span;
} level[];
} zskiplistNode;
zskiplist
typedef struct zskiplist
{
// 头指针尾指针
struct zskiplistNode *header, *tail;
// 跳表包含多少个节点(除去表头结点)
unsigned long length;
// 跳表的层数,当前最高层数
int level;
} zskiplist;
跳表相关操作
查询
//获取跳表的表头
x = zsl->header;
//从最大层数开始逐一遍历
for (i = zsl->level-1; i >= 0; i--) {
...
// 前提:在第i层,x不是尾节点
// 条件1:x在第i层的下一个节点score小于当前传递进来的score
// 条件2:score相同,但是下一个节点的ele sds小于 传进来的ele
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;
}
...
}
插入
插入一个节点的操作相对来说是比较复杂的,可以拆解成如下几个部分:
- 查询并保存每一层要插入的位置信息
- 构造新节点,插入
- 调整前向、后向和span信息
/* 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)
{
// update数组用来存储在本层中每一个新要插入节点的位置的前一个节点,也就是降层节点
// x作为遍历的辅助指针
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
// rank数组来记录遍历查询节点的过程中每层跨越的长度
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level;
serverAssert(!isnan(score));
// 先获取跳表的header节点
x = zsl->header;
// 从上到下开始遍历
for (i = zsl->level - 1; i >= 0; i--)
{
/* store rank that is crossed to reach the insert position */
// 如果是最高层则rank[i] = 0
// 否则,这里需要注意的是rank[i]起始值都等于rank[i + 1]的值,那么层层累加
// 最后rank[0] + 1就是新节点 前置节点的排位
rank[i] = i == (zsl->level - 1) ? 0 : rank[i + 1];
// 1.新插入的节点的score是否大于前向节点的score
// 2.score相同的时候判断ele大小
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;
// 移动到forward指向的节点
x = x->level[i].forward;
}
// 表示找到了每一层上的下沉节点位置(这一层中将要插入节点的前一个位置),将其更新到update数组中
update[i] = x;
}
/* we assume the element is not already inside, since we allow duplicated
* scores, reinserting the same element should never happen since the
* caller of zslInsert() should test in the hash table if the element is
* already inside or not. */
// 随机生成一个层数
level = zslRandomLevel();
// 如果生成的层数大于现有层数,就要调整多出来的那些层的信息
if (level > zsl->level)
{
for (i = zsl->level; i < level; i++)
{
// rank暂时置0
rank[i] = 0;
// 下称节点暂时为头节点
update[i] = zsl->header;
// 下沉节点的跨度暂时为整个链表的长度
update[i]->level[i].span = zsl->length;
}
// 调整整个的链表level
zsl->level = level;
}
// 创建一个新的node
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]代表:第i层的降层节点和要插入节点之间的跨度
// update[i] -> level[i].span - (rank[0] - rank[i]): 第i层要插入的节点与forward之间的距离
x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
// 降层节点的跨度也要更新 + 1
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++;
}
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;
}
删除
首先, zslDelete()函数的大部分查找功能和数组都和插入函数比较类似,大致过程如下:
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;
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 */
}
重点主要是zslDeleteNode()函数:
/* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank */
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update)
{
int i;
for (i = 0; i < zsl->level; i++)
{
// 如果是要删除的节点
if (update[i]->level[i].forward == x)
{
// x在本层的跨度:x到其forward节点之间的距离,把这一部分加到下沉节点的span上,并 - 1
update[i]->level[i].span += x->level[i].span - 1;
// 等价于p.next = p.next.next
update[i]->level[i].forward = x->level[i].forward;
}
else
{
// 降层节点指向的forward节点不是要删除的节点,span置为-1
update[i]->level[i].span -= 1;
}
}
if (x->level[0].forward)
{
// 如果删除的节点的forward节点在最底层不是空,那么改变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--;
}
范围查询
/* Find the first node that is contained in the specified range.
* Returns NULL when no element is contained in the range. */
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec *range)
{
zskiplistNode *x;
int i;
/* If everything is out of range, return early. */
if (!zslIsInRange(zsl, range))
return NULL;
x = zsl->header;
// 从最高层开始查找
for (i = zsl->level - 1; i >= 0; i--)
{
/* Go forward while *OUT* of range. */
// 当x前向节点存在并且x的forward的节点score不在range范围内(比min小),继续向后移动
while (x->level[i].forward &&
!zslValueGteMin(x->level[i].forward->score, range))
x = x->level[i].forward;
}
/* This is an inner range, so the next node cannot be NULL. */
// 找到了第一个在范围内的点
x = x->level[0].forward;
serverAssert(x != NULL);
/* Check if score <= max. */
if (!zslValueLteMax(x->score, range))
return NULL;
return x;
}
/* Find the last node that is contained in the specified range.
* Returns NULL when no element is contained in the range. */
zskiplistNode *zslLastInRange(zskiplist *zsl, zrangespec *range)
{
zskiplistNode *x;
int i;
/* If everything is out of range, return early. */
if (!zslIsInRange(zsl, range))
return NULL;
x = zsl->header;
// 也是从最高层开始寻找
for (i = zsl->level - 1; i >= 0; i--)
{
/* Go forward while *IN* range. */
// 当这一层上x的forward节点的score小于max,沿着本层继续搜寻,否则降层
while (x->level[i].forward &&
zslValueLteMax(x->level[i].forward->score, range))
x = x->level[i].forward;
}
/* This is an inner range, so this node cannot be NULL. */
serverAssert(x != NULL);
/* Check if score >= min. */
if (!zslValueGteMin(x->score, range))
return NULL;
return x;
}