Redis基础-跳表

 

一、跳表原理

 

1.1、什么是跳表

 跳跃表是一种随机化的数据结构,在查找、插入和删除这些字典操作上,其效率可比拟于平衡二叉树(如红黑树),大多数操作只需要O(log n)平均时间,但它的代码以及原理更简单。跳跃表的定义如下:
        “Skip lists are data structures  that use probabilistic  balancing rather than  strictly  enforced balancing. As a result, the algorithms for insertion and deletion in skip lists  are much simpler and significantly  faster  than  equivalent  algorithms for balanced trees.”
        译文:跳跃表使用概率平衡,而不是强制平衡,因此,对于插入和删除结点比传统上的平衡树算法更为简洁高效。 
 

1.2、理想跳表

 
在理想情况下:
  • 最底层元素是一个单链表
  • 从最底层往上,每一层的元素个数是之前一层的1/2
 
 
 
 
 

二、跳表redis实现

 
 
Base:redis version: 5.0.8
 

2.1、redis跳表原理和数据结构

 
 
 

2.2、Redis跳表代码

2.2.1、Redis跳表数据结构

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#跳表节点
typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward;
    #跳表表示level的数据
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span;
    } level[];
} zskiplistNode;
  
#跳表 list结构
typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;
  
#zset 结构
typedef struct zset {
    dict *dict;
    zskiplist *zsl;
} zset;

  

 

 

2.2.2、跳表执行流程

 
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#zset 添加/更新操作入口
int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
    /* Turn options into simple to check vars. */
    int incr = (*flags & ZADD_INCR) != 0;
    int nx = (*flags & ZADD_NX) != 0;
    int xx = (*flags & ZADD_XX) != 0;
    *flags = 0; /* We'll return our response flags. */
    double curscore;
  
  
    /* NaN as input is an error regardless of all the other parameters. */
    if (isnan(score)) {
        *flags = ZADD_NAN;
        return 0;
    }
  
  
    /* Update the sorted set according to its encoding. */
    if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *eptr;
  
  
        if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) {
            /* NX? Return, same element already exists. */
            if (nx) {
                *flags |= ZADD_NOP;
                return 1;
            }
  
  
            /* Prepare the score for the increment if needed. */
            if (incr) {
                score += curscore;
                if (isnan(score)) {
                    *flags |= ZADD_NAN;
                    return 0;
                }
                if (newscore) *newscore = score;
            }
  
  
            /* Remove and re-insert when score changed. */
            if (score != curscore) {
                zobj->ptr = zzlDelete(zobj->ptr,eptr);
                zobj->ptr = zzlInsert(zobj->ptr,ele,score);
                *flags |= ZADD_UPDATED;
            }
            return 1;
        } else if (!xx) {
            /* Optimize: check if the element is too large or the list
             * becomes too long *before* executing zzlInsert. */
            zobj->ptr = zzlInsert(zobj->ptr,ele,score);
            if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries ||
                sdslen(ele) > server.zset_max_ziplist_value)
                zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
            if (newscore) *newscore = score;
            *flags |= ZADD_ADDED;
            return 1;
        } else {
            *flags |= ZADD_NOP;
            return 1;
        }
    } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
        #当前zset编码是 skipList
        zset *zs = zobj->ptr;
        zskiplistNode *znode;
        dictEntry *de;
        #根据 ele从dict中获取元素,如果获取到直接更新该节点value,如果获取不到就插入
        de = dictFind(zs->dict,ele);
        if (de != NULL) {
            /* NX? Return, same element already exists. */
            if (nx) {
                *flags |= ZADD_NOP;
                return 1;
            }
            curscore = *(double*)dictGetVal(de);
  
  
            /* Prepare the score for the increment if needed. */
            if (incr) {
                score += curscore;
                if (isnan(score)) {
                    *flags |= ZADD_NAN;
                    return 0;
                }
                if (newscore) *newscore = score;
            }
  
  
            /* Remove and re-insert when score changes. */
            if (score != curscore) {
                //更新znode节点
                znode = zslUpdateScore(zs->zsl,curscore,ele,score);
                /* Note that we did not removed the original element from
                 * the hash table representing the sorted set, so we just
                 * update the score. */
                dictGetVal(de) = &znode->score; /* Update score ptr. */
                *flags |= ZADD_UPDATED;
            }
            return 1;
        } else if (!xx) {
            ele = sdsdup(ele);
            //插入新的节点
            znode = zslInsert(zs->zsl,score,ele);
            serverAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK);
            *flags |= ZADD_ADDED;
            if (newscore) *newscore = score;
            return 1;
        } else {
            *flags |= ZADD_NOP;
            return 1;
        }
    } else {
        serverPanic("Unknown sorted set encoding");
    }
    return 0; /* Never reached. */
}

  

 
 
 
插入新的节点流程 
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
64
65
66
67
68
69
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned int rank[ZSKIPLIST_MAXLEVEL];
    int i, level;
  
  
    serverAssert(!isnan(score));
    #初始化遍历初始值
    #rank 数组收集每层level从header到小于score的最后一个节点的span值
    #update 数组收集每层level 小于score的最后一个节点
    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
        /* store rank that is crossed to reach the insert position */
        #rank[level-]的初始值为0 ,是因为 当前是计算的第一个元素,需要从header.level[i]中取出来
        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值,如果当前值大于当前list的level值,需要初始化[当前level,level)之间的level元素
    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);
    //从下到上,修改每层小雨score的最后一个元素 ,与当前元素level[i]的链接关系以及span数值
    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 */
    #修改每一层小于score的最后一个元素的span值,在这里再执行一次是因为当前节点的level如果小于list的level,则大于当前节点level到list level之间的level的span值还没有被处理,而这些数值只是单纯的增加1
    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;
    #修改当前list 元素个数
    zsl->length++;
    return x;
}

  

 
 
 
 
跳表level函数:
1
2
3
4
5
6
7
8
#define ZSKIPLIST_P 0.25    
#define ZSKIPLIST_MAXLEVEL 64 /* Should be enough for 2^64 elements */
int zslRandomLevel(void) {
    int level = 1;
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

  

redis中规定,level最多只能有64层
 
 

三、跳表java实现

 

3.1、数据结构

 
java实现时去掉了dict结构,主要演示node的插入
 
 
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
64
65
@Data
public class  ZSkipListNode {
  
  
    private String ele;
    private double score;
    private ZSkipListNode backward;
    private ZSkipListLevel[] level;
  
  
    public ZSkipListNode(String ele, double score) {
        this.ele = ele;
        this.score = score;
        this.level = new ZSkipListLevel[ZSkipList.MAX_LEVEL];
        for(int j = 0;j< ZSkipList.MAX_LEVEL;j++){
            level[j] = new ZSkipListLevel();
        }
    }
  
  
    @Override
    public String toString() {
        return "ZSkipListNode{}";
    }
}
  
  
  
  
@Data
public class ZSkipListLevel {
    private ZSkipListNode forword;
    private long span;
  
  
    @Override
    public String toString() {
        return "ZSkipListLevel{}";
    }
}
  
@Data
public class ZSkipList {
    public static final int MAX_LEVEL = 32;
    public static final double LEVEL_FACTOR = 0.25;
    private ZSkipListNode header;
    private ZSkipListNode tail;
    private long length;
    private int level;
  
  
    public ZSkipList() {
        this.level = 1;
        this.length = 0;
        this.header = new ZSkipListNode(null,0);
        for(int j = 0;j<MAX_LEVEL;j++){
            this.getHeader().getLevel()[j].setForword(null);
            this.getHeader().getLevel()[j].setSpan(0);
        }
        this.getHeader().setBackward(null);
        this.setTail(null);
    }
  
}

  

3.2、insert和delete方法实现
 
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
@Data
public class ZSkipList {
    public static final int MAX_LEVEL = 32;
    public static final double LEVEL_FACTOR = 0.25;
    private ZSkipListNode header;
    private ZSkipListNode tail;
    private long length;
    private int level;
  
  
    public ZSkipList() {
        this.level = 1;
        this.length = 0;
        this.header = new ZSkipListNode(null,0);
        for(int j = 0;j<MAX_LEVEL;j++){
            this.getHeader().getLevel()[j].setForword(null);
            this.getHeader().getLevel()[j].setSpan(0);
        }
        this.getHeader().setBackward(null);
        this.setTail(null);
    }
  
  
    public ZSkipListNode insert(double score, String ele) {
        long[] rank = new long[MAX_LEVEL];
        ZSkipListNode[] update = new ZSkipListNode[MAX_LEVEL];
        ZSkipListNode h = this.getHeader();
        for(int i=getLevel()-1;i>=0;i--){
            rank[i] = i == getLevel()-1 ? 0 : rank[i+1];
            while (h.getLevel()[i].getForword()!=null
                    && (h.getLevel()[i].getForword().getScore()<score
                            || (h.getLevel()[i].getForword().getScore() == score && valCmp(h.getLevel()[i].getForword().getEle(),ele)<0))){
                           rank[i]+= h.getLevel()[i].getSpan();
                           h = h.getLevel()[i].getForword();
            }
            update[i] = h;
        }
        int level  = zslRandomLevel();
        if(level>getLevel()){
            for(int l = getLevel();l<level;l++){
                update[l] = getHeader();
                update[l].getLevel()[l].setSpan(getLength());
                rank[l]= 0;
            }
        }
        ZSkipListNode cur = new ZSkipListNode(ele,score);
        for(int i = 0;i<level;i++){
            cur.getLevel()[i].setForword(update[i].getLevel()[i].getForword());
            update[i].getLevel()[i].setForword(cur);
            cur.getLevel()[i].setSpan(update[i].getLevel()[i].getSpan()-(rank[0]-rank[i]));
            update[i].getLevel()[i].setSpan(rank[0]-rank[i]+1);
        }
  
  
        for (int i = level;i<this.getLevel()-1; i++){
            update[i].getLevel()[i].setSpan(update[i].getLevel()[i].getSpan()+1);
        }
        cur.setBackward(update[0] == getHeader() ? null  : update[0]);
  
  
        if(cur.getLevel()[0].getForword()!=null){
            cur.getLevel()[0].getForword().setBackward(cur);
        }else {
            this.setTail(cur);
        }
        if(level>getLevel()){
            this.setLevel(level);
        }
        this.setLength(this.getLength()+1);
        return cur;
    }
  
  
    /**
     * 删除成功 返回1 ,删除失败,或者没有找到返回0
     * 删除元素
     * 并修改剩下的level中的 span信息
     * @return
     */
    public int delete(ZSkipListNode node,ZSkipListNode[] update){
        for(int i=0;i<getLevel();i++){
            ZSkipListLevel level =update[i].getLevel()[i];
            if(level.getForword() == node){
                level.setSpan(level.getSpan()+node.getLevel()[i].getSpan()-1);
                level.setForword(node.getLevel()[i].getForword());
            }else {
                 level.setSpan(level.getSpan()-1);
            }
        }
  
  
  
  
        /**
         * 修改返回指针
         */
        if(node.getLevel()[0].getForword()==null){
            this.setTail(node.getBackward());
        }else {
            node.getLevel()[0].getForword().setBackward(node.getBackward());
        }
  
  
        /**
         * 修改level
         */
  
  
        while (getLevel()>0 && getHeader().getLevel()[getLevel()-1].getForword()==null){
            setLevel(getLevel()-1);
        }
        setLength(getLength()-1);
        return 1;
    }
  
  
  
  
    /**
     * int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node)
     * @param score
     * @param ele
     * @return
     */
    public int deleteWithScore(double score,String ele){
         ZSkipListNode[] update = new ZSkipListNode[MAX_LEVEL];
         ZSkipListNode h = getHeader();
         ZSkipListNode x = null;
         for(int i= getLevel()-1;i>=0;i--){
             while (h.getLevel()[i].getForword() != null
                     && (h.getLevel()[i].getForword().getScore() < score
                             || (h.getLevel()[i].getForword().getScore() == score && valCmp(h.getLevel()[i].getForword().getEle(), ele) < 0))
             ) {
                 h = h.getLevel()[i].getForword();
             }
             update[i]=h;
         }
         x = h.getLevel()[0].getForword();
         if(x!=null && x.getScore() == score && valCmp(x.getEle(),ele) == 0){
             return delete(x,update);
         }
         return 0;
    }
  
  
  
  
    private int zslRandomLevel() {
        int level = 1;
        Random random = new Random();
  
  
        while(random.nextInt(20)<20*LEVEL_FACTOR){
            level+=1;
        }
        return level<MAX_LEVEL ? level : MAX_LEVEL;
    }
  
  
    public int valCmp(String t, String target) {
        return t.compareTo(target);
    }
  
  
  
  
    public static void main(String[] args) {
        ZSkipList list = new ZSkipList();
        Map<String,Integer>  eleMap = new HashMap<>();
        Random random = new Random();
        int max = 0;
        for (int i = 0;i<10;i++){
            int score =1+random.nextInt(20);
            max +=score;
            String ele = "a"+i;
            list.insert(max,ele);
            eleMap.put(ele,max);
        }
        printZsl(list);
  
  
        /**
         * 删除元素测试
         */
  
  
        for (Map.Entry<String,Integer> entry : eleMap.entrySet()){
            list.deleteWithScore(entry.getValue(),entry.getKey());
            System.out.printf("delete,ele:"+entry.getKey()+",score:"+entry.getValue());
            printZsl(list);
        }
    }
  
  
    public static void printZsl(ZSkipList list){
        ZSkipListNode header = list.getHeader();
        System.out.println("---------------------------------------");
        for(int i = list.getLevel()-1;i>=0;i--){
            System.out.println("level = "+i);
            ZSkipListNode h = header.getLevel()[i].getForword();
            while (h!=null){
                System.out.printf("[node:"+h.getEle()+":"+h.getScore()+",span:"+h.getLevel()[i].getSpan()+"]-");
                h = h.getLevel()[i].getForword();
            }
            System.out.println();
        }
        System.out.println("---------------------------------------");
  
  
    }
  
  
    @Override
    public String toString() {
        return "ZSkipList{";
    }
}

  

 
 
 
 
 
 
 
 
 
 
 
posted @   程序员老徐  阅读(49)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示