更新中……
前戏skiplist:
在讲redis的hash数据结构之前我们先了解下skiplist
Wikipedia给出的解释如下:
跳跃列表(skiplist)是一种数据结构。它允许快速查询一个有序连续元素的数据链表。跳跃列表的平均查找和插入时间复杂度都是O(log n),优于普通队列的O(n)。
通俗的讲就是:跳跃表是一种有序的数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
skiplist的插入流程如下
在这里我们就不继续深讨这个算法了。
Redis中的skiplist
在redis中,
skiplist的定义
如下(server.h):
1 | //注意4.x版本的ZSKIPLIST_MAXLEVEL 还是32 |
在redis的源码中,作者为了满身redis的要求,对skiplist进行了修改:
- 允许重复的 score 值:多个不同的 member 的 score 值可以相同,当score相同时根据member(代码里的ele)的字典序来排名。
- 进行对比操作时,不仅要检查 score 值,还要检查 member :当 score 值可以重复时,单靠 score 值无法判断一个元素的身份,所以需要连 member 域都一并检查才行。
- 每个节点都带有一个高度为 1 层的后退指针,用于从表尾方向向表头方向迭代:当执行 ZREVRANGE 或 ZREVRANGEBYSCORE 这类以逆序处理有序集的命令时,就会用到这个属性。
zskiplist的相关接口(z_set.c)
创建一个zskiplist
时间复杂度O(1)
1 |
|
zskiplist插入一个节点
时间复杂度O(logn)
插入节点的流程如下:
1 |
|
zskiplist删除一个节点
时间复杂度O(logn)
释放一个节点的内存 时间复杂度O(1):
1 | void zslFreeNode(zskiplistNode *node) { |
释放整个skiplist的内存 时间复杂度O(n):
1 | /* Free a whole skiplist. */ |
从skiplist中删除并释放掉一个节点 时间复杂度O(logn):
主要分为以下3个步骤:
- 根据member(ele)和score找到节点的位置(代码里变量x即为该节点,update记录每层x的上一个节点)
- 调动zslDeleteNode把x节点从skiplist逻辑上删除
- 释放x节点内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63/* Delete an element with matching score/element from the skiplist.
* The function returns 1 if the node was found and deleted, otherwise
* 0 is returned.
*
* If 'node' is NULL the deleted node is freed by zslFreeNode(), otherwise
* it is not freed (but just unlinked) and *node is set to the node pointer,
* so that it is possible for the caller to reuse the node (including the
* referenced SDS string at node->ele). */
//从skiplist逻辑上删除一个节点并释放该节点的内存
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 */
}
/* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank */
//从skiplist逻辑上删除一个节点(不释放内存,仅改变节点位置关系)
//x为要删除的节点
//update为每一层x的上一个节点(为了更新x上一个节点的forward和span属性)
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;
}
}
//是否是tail节点
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--;
}
v1.5.2