redis底层数据结构之跳表(skiplist)
跳表(跳跃表, skiplist)
跳跃表(skiplist)是用于有序元素序列快速搜索查找的数据结构,跳表是一个随机化的数据结构,实质是一种可以进行二分查找的、具有层次结构的有序链表
跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找,平均期望的查找、插入、删除时间复杂度都是O(logn),同时支持范围查询
具有如下性质:
1) 每个节点由多层组成,排列顺序为由高层到底层
2) 每一层都是一个有序链表
3) 最底层的链表包含了所有的元素
4) 如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现
5) 每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点
6) 分数(score)允许重复,即key是允许重复的
1 zskiplist结构
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
其中:
header: 表头节点,没有存储实际的数据,而是一个空的,初始化层级数为ZSKIPLIST_MAXLEVEL值(默认32)的节点
tail: 表尾节点
length: 节点数量
level: 最大层级,表头节点层数不计
2 zskiplistNode结构
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned long span;
} level[];
} zskiplistNode;
其中:
ele:节点对象,即节点数据
score:当前节点值对应的分值,用于排序,按分值从小到大来排序,各个节点对象必须是唯一的, 但是多个节点保存的分值却可以是相同的, 分值相同的节点按照成员对象值从小到大排序
backward:当前节点的上一个节点
level:表示层数
forward:同一层的下一个节点
span:跨度,当前节点到forward指向的节点跨越了多少个节点
3 skiplist示意图
4 skiplist插入节点
步骤如下:
1) 找到每一层新节点要插入的位置(update):从高层到低层遍历skiplist,找到每一层小于新节点score的最大的节点,如果节点score相等,则比较ele
2) 随机分配一个层数(level),如果层数比skiplist的层数大,增加skiplist的层数并修改新加层数的span
3) 插入新节点(x):新节点每一层(x->level[i]) 的forward修改为每一层插入位置(update[i]->level[i]) 的forward,每一层插入插入位置的forward修改为新节点(顺序不能反),更新新节点(x),插入位置节点(update[i]->level[i]) 的每一层的span
4) 修改更高的level的span
5) 修改新节点的backward(x->backward)
6) 修改新节点最低层后一个节点(x->level[0].forward)的backward:如果新节点的forward不为null,则新节点后一个节点的backward为新节点;否则新节点是skiplist的尾结点
7) 修改skiplist的节点数量
src/t_zset.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 long 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; }
5 skiplist删除节点
步骤如下:
1) 找到每一层要删除节点(x) 的前一个节点(update):从高层到低层遍历skiplist,每一层都找到小于新节点score的最大的节点,如果节点score相等,则比较ele
2) 删除节点,更新每一层要删除节点前一个节点(udpate[i]) 的span和forward
3) 修改要删除节点后一个节点最低层([x->level[0].forward->backward]) 的backward:如果删除节点最低层的forward不为null,则要删除节点后一个节点最低层的backward为要删除节点的backward,否则要删除节点后一个节点是skiplist的尾结点
4) 修改skiplist的层数level
5) 修改skiplist的节点个数
src/t_zset.c文件对应的源码如下:
/* Internal function used by zslDelete, zslDeleteRangeByScore and * zslDeleteRangeByRank. */ 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--; }