目录
1.跳跃表节点 zskiplistNode -源码位置 redis/src/server.h
2.跳跃表结构 zskiplist -源码位置 redis/src/server.h
3.zset 有序集合的新增 -源码位置redis/src/t_zet.c
4..zset 有序集合的删除 -源码位置redis/src/t_zet.c
5.删除跳跃表 -源码位置redis/src/t_zet.c
说到跳跃表需要先对有序链表做一定了解
1.有序链表
我们知道在有序链表中元素都是有序排列的,平均时间复杂度是O(N), 并且每个节点都有指向下一节点的指针,最后一个节点指向NULL ,查询时间复杂度是O(n)。 插入和删除的操作也都是需要找到合适位置再做next 指针的修改操作。
对于图中的有序链表,如果我们要查询到值为27的节点,是需要比较4次才可以。 2 --> 14--> 19--> 27
那如果对于有序链表我们做了分层,每一层都是一个有序链表,并且在查找时候从最高层级开始,比较节点后如果大于查找值或是Next 指向NULL ,则降一层级查找。这样查询的方式效率会有部分提升的。
2.跳跃表
在跳跃表中查询值为27 的节点。
1.第一步: 比较第二层 第0个节点是2,小于 27 ,向后比较第二层第1 个节点是19,小于27 。继续比较发现19 next 指向NULL
2.第二步:跳跃到第1层值为19的节点next 指向 52,大于27 ;
3.第三步:跳跃到第0层,节点27就是要查询的节点,结束。
总结是比较了3次 。 2 --> 19 --> 52 ;
如果节点数量比较多时,跳过节点越多查询效率会大大提升。
以上就是跳跃表的设计思想。
3.跳跃表的应用
在Redis 中,跳跃表主要用于有序集合zset的底层实现 ;另一种有序集合的底层实现是压缩列表。在Redis 源码中我们可以看到两中数据结构的转换使用过程。
源码文件位置redis/src/t_zset.c
/**
* 当满足 server.zset_max_ziplist_entries == 0 或者 server.zset_max_ziplist_value
* 小于要插入元素的字符串长度 时,则使用跳跃表结构,否则使用压缩列表结构
*/
if (server.zset_max_ziplist_entries == 0 ||
server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr))
{
zobj = createZsetObject(); // 创建跳跃表结构
} else {
zobj = createZsetZiplistObject(); //创建压缩列表结构
}
/**
* 当zset 中元素个数大于 server.zset_max_ziplist_entries 或者 插入元素的字符串长度大于server.zset_max_ziplist_value 时,会将zset 的底层实现由压缩列表转为跳跃表。
* 目前在zset 转为跳跃表之后,即时元素被删除,也不会重新转为压缩列表
*/
if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries ||
sdslen(ele) > server.zset_max_ziplist_value)
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
4.源码分析
1.跳跃表节点 zskiplistNode -源码位置 redis/src/server.h
/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
sds ele; // 存储字符串类型数据
double score; // 存储排序的分值
struct zskiplistNode *backward; //后退指针,指向当前节点最底层的前一个节点,头节点指向NULL,从后向前遍历跳跃表使用
struct zskiplistLevel {
struct zskiplistNode *forward; // 指向当前节点的下一个节点,最后一个节点指向NULL
unsigned long span; //forward 指向的节点与当前节点之间的元素个数。 值越大,跳过的节点个数越多。
} level[]; // 柔性数组。每个节点的数组长度不同,生成跳跃表节点时,随机生成1~64 的值,值越大出现的概率越低
} zskiplistNode;
2.跳跃表结构 zskiplist -源码位置 redis/src/server.h
//通过跳跃表结构我们可以在O(1)的时间复杂度快速知道跳跃表的头节点、尾节点、高度、长度
typedef struct zskiplist {
struct zskiplistNode *header, /指向跳跃表头节点,这个节点的level 数组元素个数是64 ,64个元素的forward 都指向NULL,ele 为NULL ,score 为0 ,不计入总长度
*tail; //指向跳跃表尾节点
unsigned long length; // 跳跃表的长度,是统计除头节点以外的节点数
int level; //跳跃表的高度
} zskiplist;
3.zset 有序集合的新增 -源码位置redis/src/t_zet.c
/* 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;
serverAssert(!isnan(score));
// update[] 是因为在插入节点时,需要更新节点每层的前一个节点,需要将每层需要更新的节点记录在此数组中
// rank[] 记录当前层从header 节点到update[i] 节点所经历的步长 += level[i].span
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)))
{
rank[i] += x->level[i].span;
x = x->level[i].forward;
}
update[i] = x;
}
//随机获取插入节点的高度
level = zslRandomLevel();
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;
}
//插入节点,按照level 的层级循环插入
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 */
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;
}
4..zset 有序集合的删除 -源码位置redis/src/t_zet.c
/* 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) {
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;
}
}
//调整backward
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--;
}
5.删除跳跃表 -源码位置redis/src/t_zet.c
/* 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);
}
参考书籍: 《Redis 5设计与源码分析》