Redis系列之底层数据结构跳表SkipList
SkipList顾名思义,本质也是一个list数据结构,SkipList是在有序链表的基础上发展来的。如图,就是一个有序链表
如果链表元素太多了,查找就会变慢,时间复杂度会很高,是 O(n)。所以可以在有序链表的基础上,增加n层索引,如图,要查找23的位置,只需要在22->35中间查找即可,往下面一层查找,和27对比,马上就能找到
上面结构是只有两层数据,真实的SkipList是有多层的,所以查找过程不需要每个节点都查找。只需要根据每一层的索引,跳过很多中间的节点,查找速度大大提高。查找的时间复杂度可以降低到O(log n)
SkipList相对于普通的链表,查找效率更快,但是数据结构是通过添加很多层的索引实现快速查找的,所以会相对比较占用存储空间
在redis ZSet这种数据类型中,默认使用ziplist编码,如果元素数量大于等于128个,或者任一个member长度大于等于64字节,使用skiplist+dict存储,在Redis中的配置
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
SkipList实现,在Redis中没有专门的类,代码在server.h,代码链接:https://github.com/redis/redis/blob/6.0/src/server.h
/* ZSETs use a specialized version of Skiplists */
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;
跳表SkipList添加节点的方法zslInsert:https://github.com/redis/redis/blob/6.0/src/t_zset.c
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level;
serverAssert(!isnan(score));
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;
}
/* 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[i] = 0;
update[i] = zsl->header;
update[i]->level[i].span = zsl->length;
}
zsl->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++;
}
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;
}
ZSKIPLIST_MAXLEVEL默认32 定义在server.h
#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^64
elements */