redis 5.0.2 源码阅读——快速列表quicklist

一、quicklist简介

  为什么说quicklist是“二合一”呢?如果你看过STL中的deque的实现,就会知道deque是由一个map中控器和一个数组组成的数据结构,它既具有链表头尾插入便捷的优点,又有数组连续内存存储,支持下标访问的优点。Redis中是采用sdlist和ziplist来实现quicklist的,其中sdlist充当map中控器的作用,ziplist充当占用连续内存空间数组的作用。quicklist本身是一个双向无环链表,它的每一个节点都是一个ziplist。为什么这么设计呢?

  • 双向链表在插入节点上复杂度很低,但它的内存开销很大,每个节点的地址不连续,容易产生内存碎片。
  • ziplist是存储在一段连续的内存上,存储效率高,但是它不利于修改操作,插入和删除数都很麻烦,复杂度高,而且其需要频繁的申请释放内存,特别是ziplist中数据较多的情况下,搬移内存数据太费时!

Redis综合了双向链表和ziplist的优点,设计了quicklist这个数据结构,使它作为list键的底层实现。接下来,就要考虑每一个ziplist中存放的元素个数。

  • 如果每一个ziplist中的元素个数过少,内存碎片就会增多。可以按照极端情况双向链表来考虑。
  • 如果每一个ziplist中的元素个数过多,那么ziplist分配大块连续内存空间的难度就增大,同样会影响效率。

Redis的配置文件中,给出了每个ziplist中的元素个数设定,考虑使用场景需求,我们可以选择不同的元素个数。该参数设置格式如下:

1 list-max-ziplist-size -2

 

当数字为负数,表示以下含义:

  • -1 每个quicklistNode节点的ziplist字节大小不能超过4kb。(建议)
  • -2 每个quicklistNode节点的ziplist字节大小不能超过8kb。(默认配置)
  • -3 每个quicklistNode节点的ziplist字节大小不能超过16kb。(一般不建议)
  • -4 每个quicklistNode节点的ziplist字节大小不能超过32kb。(不建议)
  • -5 每个quicklistNode节点的ziplist字节大小不能超过64kb。(正常工作量不建议)

当数字为正数,表示:ziplist结构所最多包含的entry个数。最大值为 2^15。

  另外,在quicklist的源码中提到了一个LZF的压缩算法,该算法用于对quicklist的节点进行压缩操作。list的设计目的是能够存放很长的数据列表,当列表很长时,必然会占用很高的内存空间,且list中最容易访问的是两端的数据,中间的数据访问率较低,于是就可以从这个出发点来进一步节省内存用于其他操作。Redis提供了一下的配置参数,用于表示中间节点是否压缩。

1 list-compress-depth 0

compress成员对应的配置:list-compress-depth 0
后面的数字有以下含义:

  • 0 表示不压缩。(默认)
  • 1 表示quicklist列表的两端各有1个节点不压缩,中间的节点压缩。
  • 2 表示quicklist列表的两端各有2个节点不压缩,中间的节点压缩。
  • 3 表示quicklist列表的两端各有3个节点不压缩,中间的节点压缩。
  • 以此类推,最大为 216216。

通过列表键查看一下:

1 127.0.0.1:6379> RPUSH list 1 2 5 1000
2 "redis" "quicklist"(integer) 
3 127.0.0.1:6379> OBJECT ENCODING list
4 "quicklist"

quicklist结构在quicklist.c中的解释为A doubly linked list of ziplists意思为一个由ziplist组成的双向链表。

 

首先回忆下压缩列表的特点:

 

  • 压缩列表ziplist结构本身就是一个连续的内存块,由表头、若干个entry节点和压缩列表尾部标识符zlend组成,通过一系列编码规则,提高内存的利用率,使用于存储整数和短字符串。
  • 压缩列表ziplist结构的缺点是:每次插入或删除一个元素时,都需要进行频繁的调用realloc()函数进行内存的扩展或减小,然后进行数据”搬移”,甚至可能引发连锁更新,造成严重效率的损失。

接下来介绍quicklist与ziplist的关系:

  之前提到,quicklist是由ziplist组成的双向链表,链表中的每一个节点都以压缩列表ziplist的结构保存着数据,而ziplist有多个entry节点,保存着数据。相当与一个quicklist节点保存的是一片数据,而不再是一个数据。

例如:一个quicklist有4个quicklist节点,每个节点都保存着1个ziplist结构,每个ziplist的大小不超过8kb,ziplist的entry节点中的value成员保存着数据。

根据以上描述,总结出一下quicklist的特点:

  • quicklist宏观上是一个双向链表,因此,它具有一个双向链表的有点,进行插入或删除操作时非常方便,虽然复杂度为O(n),但是不需要内存的复制,提高了效率,而且访问两端元素复杂度为O(1)。
  • quicklist微观上是一片片entry节点,每一片entry节点内存连续且顺序存储,可以通过二分查找以 log2(n)log2(n) 的复杂度进行定位。

二、quicklist的结构实现

quicklist有关的数据结构定义在quicklist.h中。

 

2.1 quicklist表头结构

 1 typedef struct quicklist {
 2     //指向头部(最左边)quicklist节点的指针
 3     quicklistNode *head;
 4 
 5     //指向尾部(最右边)quicklist节点的指针
 6     quicklistNode *tail;
 7 
 8     //ziplist中的entry节点计数器
 9     unsigned long count;        /* total count of all entries in all ziplists */
10 
11     //quicklist的quicklistNode节点计数器
12     unsigned int len;           /* number of quicklistNodes */
13 
14     //保存ziplist的大小,配置文件设定,占16bits
15     int fill : 16;              /* fill factor for individual nodes */
16 
17     //保存压缩程度值,配置文件设定,占16bits,0表示不压缩
18     unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
19 } quicklist;

在quicklist表头结构中,有两个成员是fill和compress,其中” : “是位域运算符,表示fill占int类型32位中的16位,compress也占16位。fill和compress的配置文件是redis.conf。

2.2 quicklist节点结构

 1 typedef struct quicklistNode {
 2     struct quicklistNode *prev;     //前驱节点指针
 3     struct quicklistNode *next;     //后继节点指针
 4 
 5     //不设置压缩数据参数recompress时指向一个ziplist结构
 6     //设置压缩数据参数recompress指向quicklistLZF结构
 7     unsigned char *zl;
 8 
 9     //压缩列表ziplist的总长度
10     unsigned int sz;                  /* ziplist size in bytes */
11 
12     //ziplist中包的节点数,占16 bits长度
13     unsigned int count : 16;          /* count of items in ziplist */
14 
15     //表示是否采用了LZF压缩算法压缩quicklist节点,1表示压缩过,2表示没压缩,占2 bits长度
16     unsigned int encoding : 2;        /* RAW==1 or LZF==2 */
17 
18     //表示一个quicklistNode节点是否采用ziplist结构保存数据,2表示压缩了,1表示没压缩,默认是2,占2bits长度
19     unsigned int container : 2;       /* NONE==1 or ZIPLIST==2 */
20 
21     //标记quicklist节点的ziplist之前是否被解压缩过,占1bit长度
22     //如果recompress为1,则等待被再次压缩
23     unsigned int recompress : 1; /* was this node previous compressed? */
24 
25     //测试时使用
26     unsigned int attempted_compress : 1; /* node can't compress; too small */
27 
28     //额外扩展位,占10bits长度
29     unsigned int extra : 10; /* more bits to steal for future usage */
30 } quicklistNode;

2.3 压缩过的ziplist结构—quicklistLZF

当指定使用lzf压缩算法压缩ziplist的entry节点时,quicklistNode结构的zl成员指向quicklistLZF结构

1 typedef struct quicklistLZF {
2     //表示被LZF算法压缩后的ziplist的大小
3     unsigned int sz; /* LZF size in bytes*/
4 
5     //保存压缩后的ziplist的数组,柔性数组
6     char compressed[];
7 } quicklistLZF;

2.4 管理ziplist信息的结构quicklistEntry

和压缩列表一样,entry结构在储存时是一连串的内存块,需要将其每个entry节点的信息读取到管理该信息的结构体中,以便操作。在quicklist中定义了自己的结构。

 1 //管理quicklist中quicklistNode节点中ziplist信息的结构
 2 typedef struct quicklistEntry {
 3     const quicklist *quicklist;   //指向所属的quicklist的指针
 4     quicklistNode *node;          //指向所属的quicklistNode节点的指针
 5     unsigned char *zi;            //指向当前ziplist结构的指针
 6     unsigned char *value;         //指向当前ziplist结构的字符串vlaue成员
 7     long long longval;            //指向当前ziplist结构的整数value成员
 8     unsigned int sz;              //保存当前ziplist结构的字节数大小
 9     int offset;                   //保存相对ziplist的偏移量
10 } quicklistEntry;

基于以上结构信息,我们可以得出一个quicklist结构,在空间中的大致可能的样子:

 

 

 2.5 迭代器结构实现

在redis的quicklist结构中,实现了自己的迭代器,用于遍历节点。

1 //quicklist的迭代器结构
2 typedef struct quicklistIter {
3     const quicklist *quicklist;   //指向所属的quicklist的指针
4     quicklistNode *current;       //指向当前迭代的quicklist节点的指针
5     unsigned char *zi;            //指向当前quicklist节点中迭代的ziplist
6     long offset;                  //当前ziplist结构中的偏移量      /* offset in current ziplist */
7     int direction;                //迭代方向
8 } quicklistIter;

三、quicklist的部分操作源码注释

quicklist.c和quicklist.h文件的注释:redis 源码注释

3.1 插入一个entry节点

quicklist的插入:以一个已存在的entry前或后插入一个entry节点,非常的复杂,因为情况非常多。

  • 当前quicklistNode节点的ziplist可以插入。
    • 插入在已存在的entry前
    • 插入在已存在的entry后
  • 如果当前quicklistNode节点的ziplist由于fill的配置,无法继续插入。
    • 已存在的entry是ziplist的头节点,当前quicklistNode节点前驱指针不为空,且是尾插
    • 前驱节点可以插入,因此插入在前驱节点的尾部。
    • 前驱节点不可以插入,因此要在当前节点和前驱节点之间新创建一个新节点保存要插入的entry。
    • 已存在的entry是ziplist的尾节点,当前quicklistNode节点后继指针不为空,且是前插
    • 后继节点可以插入,因此插入在前驱节点的头部。
    • 后继节点不可以插入,因此要在当前节点和后继节点之间新创建一个新节点保存要插入的entry。
    • 以上情况不满足,则属于将entry插入在ziplist中间的任意位置,需要分割当前quicklistNode节点。最后如果能够合并,还要合并

 插入函数

  1 /* Insert a new entry before or after existing entry 'entry'.
  2  *
  3  * If after==1, the new value is inserted after 'entry', otherwise
  4  * the new value is inserted before 'entry'. */
  5 //如果after为1,在已存在的entry后插入一个entry,否则在前面插入
  6 REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry,
  7                                    void *value, const size_t sz, int after) {
  8     int full = 0, at_tail = 0, at_head = 0, full_next = 0, full_prev = 0;
  9     int fill = quicklist->fill;
 10     quicklistNode *node = entry->node;
 11     quicklistNode *new_node = NULL;
 12 
 13     if (!node) {    //如果entry为没有所属的quicklistNode节点,需要新创建
 14         /* we have no reference node, so let's create only node in the list */
 15         D("No node given!");
 16         new_node = quicklistCreateNode();   //创建一个节点
 17         //将entry值push到new_node新节点的ziplist中
 18         new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
 19         //将新的quicklistNode节点插入到quicklist中
 20         __quicklistInsertNode(quicklist, NULL, new_node, after);
 21         //更新entry计数器
 22         new_node->count++;
 23         quicklist->count++;
 24         return;
 25     }
 26 
 27     /* Populate accounting flags for easier boolean checks later */
 28     //如果node不能插入entry
 29     if (!_quicklistNodeAllowInsert(node, fill, sz)) {
 30         D("Current node is full with count %d with requested fill %lu",
 31           node->count, fill);
 32         full = 1;   //设置full的标志
 33     }
 34 
 35     //如果是后插入且当前entry为尾部的entry
 36     if (after && (entry->offset == node->count)) {
 37         D("At Tail of current ziplist");
 38         at_tail = 1;    //设置在尾部at_tail标示
 39         //如果node的后继节点不能插入
 40         if (!_quicklistNodeAllowInsert(node->next, fill, sz)) {
 41             D("Next node is full too.");
 42             full_next = 1;  //设置标示
 43         }
 44     }
 45 
 46     //如果是前插入且当前entry为头部的entry
 47     if (!after && (entry->offset == 0)) {
 48         D("At Head");
 49         at_head = 1;    //设置at_head表示
 50         if (!_quicklistNodeAllowInsert(node->prev, fill, sz)) { //如果node的前驱节点不能插入
 51             D("Prev node is full too.");
 52             full_prev = 1;      //设置标示
 53         }
 54     }
 55 
 56     /* Now determine where and how to insert the new element */
 57     //如果node不满,且是后插入
 58     if (!full && after) {
 59         D("Not full, inserting after current position.");
 60         quicklistDecompressNodeForUse(node);    //将node临时解压
 61         unsigned char *next = ziplistNext(node->zl, entry->zi); //返回下一个entry的地址
 62         if (next == NULL) { //如果next为空,则直接在尾部push一个entry
 63             node->zl = ziplistPush(node->zl, value, sz, ZIPLIST_TAIL);
 64         } else {            //否则,后插入一个entry
 65             node->zl = ziplistInsert(node->zl, next, value, sz);
 66         }
 67         node->count++;  //更新entry计数器
 68         quicklistNodeUpdateSz(node);    //更新ziplist的大小sz
 69         quicklistRecompressOnly(quicklist, node);   //将临时解压的重压缩
 70 
 71     //如果node不满且是前插
 72     } else if (!full && !after) {
 73         D("Not full, inserting before current position.");
 74         quicklistDecompressNodeForUse(node);    //将node临时解压
 75         node->zl = ziplistInsert(node->zl, entry->zi, value, sz);   //前插入
 76         node->count++;  //更新entry计数器
 77         quicklistNodeUpdateSz(node);     //更新ziplist的大小sz
 78         quicklistRecompressOnly(quicklist, node);   //将临时解压的重压缩
 79 
 80     //当前node满了,且当前已存在的entry是尾节点,node的后继节点指针不为空,且node的后驱节点能插入
 81     //本来要插入当前node中,但是当前的node满了,所以插在next节点的头部
 82     } else if (full && at_tail && node->next && !full_next && after) {
 83         /* If we are: at tail, next has free space, and inserting after:
 84          *   - insert entry at head of next node. */
 85         D("Full and tail, but next isn't full; inserting next node head");
 86         new_node = node->next;  //new_node指向node的后继节点
 87         quicklistDecompressNodeForUse(new_node);    //将node临时解压
 88         new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_HEAD);  //在new_node头部push一个entry
 89         new_node->count++;  //更新entry计数器
 90         quicklistNodeUpdateSz(new_node);    //更新ziplist的大小sz
 91         quicklistRecompressOnly(quicklist, new_node);   //将临时解压的重压缩
 92 
 93     //当前node满了,且当前已存在的entry是头节点,node的前驱节点指针不为空,且前驱节点可以插入
 94     //因此插在前驱节点的尾部
 95     } else if (full && at_head && node->prev && !full_prev && !after) {
 96         /* If we are: at head, previous has free space, and inserting before:
 97          *   - insert entry at tail of previous node. */
 98         D("Full and head, but prev isn't full, inserting prev node tail");
 99         new_node = node->prev;  //new_node指向node的后继节点
100         quicklistDecompressNodeForUse(new_node);    //将node临时解压
101         new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_TAIL);//在new_node尾部push一个entry
102         new_node->count++;  //更新entry计数器
103         quicklistNodeUpdateSz(new_node);    //更新ziplist的大小sz
104         quicklistRecompressOnly(quicklist, new_node);   //将临时解压的重压缩
105 
106     //当前node满了
107     //要么已存在的entry是尾节点,且后继节点指针不为空,且后继节点不可以插入,且要后插
108     //要么已存在的entry为头节点,且前驱节点指针不为空,且前驱节点不可以插入,且要前插
109     } else if (full && ((at_tail && node->next && full_next && after) ||
110                         (at_head && node->prev && full_prev && !after))) {
111         /* If we are: full, and our prev/next is full, then:
112          *   - create new node and attach to quicklist */
113         D("\tprovisioning new node...");
114         new_node = quicklistCreateNode();   //创建一个节点
115         new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);  //将entrypush到new_node的头部
116         new_node->count++;  //更新entry计数器
117         quicklistNodeUpdateSz(new_node);        //更新ziplist的大小sz
118         __quicklistInsertNode(quicklist, node, new_node, after);    //将new_node插入在当前node的后面
119 
120     //当前node满了,且要将entry插入在中间的任意地方,需要将node分割
121     } else if (full) {
122         /* else, node is full we need to split it. */
123         /* covers both after and !after cases */
124         D("\tsplitting node...");
125         quicklistDecompressNodeForUse(node);    //将node临时解压
126         new_node = _quicklistSplitNode(node, entry->offset, after);//分割node成两块
127         new_node->zl = ziplistPush(new_node->zl, value, sz,
128                                    after ? ZIPLIST_HEAD : ZIPLIST_TAIL);//将entry push到new_node中
129         new_node->count++;  //更新entry计数器
130         quicklistNodeUpdateSz(new_node);        //更新ziplist的大小sz
131         __quicklistInsertNode(quicklist, node, new_node, after);    //将new_node插入进去
132         _quicklistMergeNodes(quicklist, node);  //左右能合并的合并
133     }
134 
135     quicklist->count++;     //更新总的entry计数器
136 }

3.2 push操作

push一个entry到quicklist**头节点或尾节点中ziplist的头部或尾部**。底层调用了ziplistPush操作。

 1 /* Add new entry to head node of quicklist.
 2  *
 3  * Returns 0 if used existing head.
 4  * Returns 1 if new head created. */
 5 //push一个entry节点到quicklist的头部
 6 //返回0表示不改变头节点指针,返回1表示节点插入在头部,改变了头结点指针
 7 int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
 8     quicklistNode *orig_head = quicklist->head; //备份头结点地址
 9 
10     //如果ziplist可以插入entry节点
11     if (likely(
12             _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
13         quicklist->head->zl =
14             ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);  //将节点push到头部
15         quicklistNodeUpdateSz(quicklist->head); //更新quicklistNode记录ziplist大小的sz
16     } else {        //如果不能插入entry节点到ziplist
17         quicklistNode *node = quicklistCreateNode();    //新创建一个quicklistNode节点
18 
19         //将entry节点push到新创建的quicklistNode节点中
20         node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
21 
22         quicklistNodeUpdateSz(node);    //更新ziplist的大小sz
23         _quicklistInsertNodeBefore(quicklist, quicklist->head, node);   //将新创建的节点插入到头节点前
24     }
25     quicklist->count++;                     //更新quicklistNode计数器
26     quicklist->head->count++;               //更新entry计数器
27     return (orig_head != quicklist->head);  //如果改变头节点指针则返回1,否则返回0
28 }
29 
30 /* Add new entry to tail node of quicklist.
31  *
32  * Returns 0 if used existing tail.
33  * Returns 1 if new tail created. */
34 //push一个entry节点到quicklist的尾节点中,如果不能push则新创建一个quicklistNode节点
35 //返回0表示不改变尾节点指针,返回1表示节点插入在尾部,改变了尾结点指针
36 int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
37     quicklistNode *orig_tail = quicklist->tail;
38 
39     //如果ziplist可以插入entry节点
40     if (likely(
41             _quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {
42         quicklist->tail->zl =
43             ziplistPush(quicklist->tail->zl, value, sz, ZIPLIST_TAIL);  //将节点push到尾部
44         quicklistNodeUpdateSz(quicklist->tail); //更新quicklistNode记录ziplist大小的sz
45     } else {
46         quicklistNode *node = quicklistCreateNode();        //新创建一个quicklistNode节点
47 
48         //将entry节点push到新创建的quicklistNode节点中
49         node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL);
50 
51         quicklistNodeUpdateSz(node);        //更新ziplist的大小sz
52         _quicklistInsertNodeAfter(quicklist, quicklist->tail, node);//将新创建的节点插入到尾节点后
53     }
54     quicklist->count++;             //更新quicklistNode计数器
55     quicklist->tail->count++;       //更新entry计数器
56     return (orig_tail != quicklist->tail);  //如果改变尾节点指针则返回1,否则返回0
57 }

上层调用函数

1 /* Wrapper to allow argument-based switching between HEAD/TAIL pop */
2 void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
3                    int where) {
4     if (where == QUICKLIST_HEAD) {
5         quicklistPushHead(quicklist, value, sz);//头插法
6     } else if (where == QUICKLIST_TAIL) {
7         quicklistPushTail(quicklist, value, sz);//尾插法
8     }
9 }

3.3 pop操作
  从quicklist的头节点或尾节点的ziplist中pop出一个entry,分该entry保存的是字符串还是整数。如果字符串的话,需要传入一个函数指针,这个函数叫_quicklistSaver(),真正的pop操作还是在这两个函数基础上在封装了一次,来操作拷贝字符串的操作。

 1 // 接口函数,执行POP操作
 2 // 执行成功返回1,反之0
 3 // 如果弹出节点是字符串值,data,sz存放弹出节点的字符串值
 4 // 如果弹出节点是整型值,slong存放弹出节点的整型值
 5 int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
 6                  unsigned int *sz, long long *slong) {
 7     unsigned char *vstr;
 8     unsigned int vlen;
 9     long long vlong;
10     // 没有数据项,直接返回
11     if (quicklist->count == 0)
12         return 0;
13     // 调用底层实现函数
14     // 传入的_quicklistSaver是一个函数指针,用于深拷贝节点的值,用于返回
15     int ret = quicklistPopCustom(quicklist, where, &vstr, &vlen, &vlong,
16                                  _quicklistSaver);
17     // 给data,sz,slong赋值
18     if (data)
19         *data = vstr;
20     if (slong)
21         *slong = vlong;
22     if (sz)
23         *sz = vlen;
24     return ret;
25 }
26 // pop操作的底层实现函数
27 // 执行成功返回1,反之0
28 // 如果弹出节点是字符串值,data,sz存放弹出节点的字符串值
29 // 如果弹出节点是整型值,slong存放弹出节点的整型值
30 int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
31                        unsigned int *sz, long long *sval,
32                        void *(*saver)(unsigned char *data, unsigned int sz)) {
33     unsigned char *p;
34     unsigned char *vstr;
35     unsigned int vlen;
36     long long vlong;
37     // 判断弹出位置,首部或者尾部
38     int pos = (where == QUICKLIST_HEAD) ? 0 : -1;
39     // 没有数据
40     if (quicklist->count == 0)
41         return 0;
42     // 
43     if (data)
44         *data = NULL;
45     if (sz)
46         *sz = 0;
47     if (sval)
48         *sval = -123456789;
49     // 获取quicklist节点
50     quicklistNode *node;
51     if (where == QUICKLIST_HEAD && quicklist->head) {
52         node = quicklist->head;
53     } else if (where == QUICKLIST_TAIL && quicklist->tail) {
54         node = quicklist->tail;
55     } else {
56         return 0;
57     }
58     // 获取ziplist中的节点
59     p = ziplistIndex(node->zl, pos);
60     // 获取该节点的值
61     if (ziplistGet(p, &vstr, &vlen, &vlong)) {
62         // 如果是字符串值
63         if (vstr) {
64             if (data)
65                 // _quicklistSaver函数用于深拷贝取出返回值
66                 *data = saver(vstr, vlen);
67             if (sz)
68                 *sz = vlen;  // 字符串的长度
69         } else {
70             // 如果存放的是整型值
71             if (data)
72                 *data = NULL;  // 字符串设为NULL
73             if (sval)
74                 *sval = vlong;  // 弹出节点的整型值
75         }
76         // 删除该节点
77         quicklistDelIndex(quicklist, node, &p);
78         return 1;
79     }
80     return 0;
81 }
82 // 返回一个字符串副本,深拷贝
83 // 这里深拷贝的用意是避免二次释放
84 REDIS_STATIC void *_quicklistSaver(unsigned char *data, unsigned int sz) {
85     unsigned char *vstr;
86     if (data) {
87         vstr = zmalloc(sz);
88         memcpy(vstr, data, sz);
89         return vstr;
90     }
91     return NULL;
92 }

四、其他接口函数

Redis关于quicklist还提供了很多接口函数。

 1 // 在quicklist尾部追加指针zl指向的ziplist
 2 void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl);
 3 // 将ziplist数据转换成quicklist
 4 quicklist *quicklistCreateFromZiplist(int fill, int compress,
 5                                       unsigned char *zl);
 6 // 在node节点后添加一个值valiue
 7 void quicklistInsertAfter(quicklist *quicklist, quicklistEntry *node,
 8                           void *value, const size_t sz);
 9 // 在node节点前面添加一个值value
10 void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *node,
11                            void *value, const size_t sz);
12 // 删除ziplist节点entry
13 void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry);
14 // 翻转quicklist
15 void quicklistRotate(quicklist *quicklist);
16 // 返回quicklist列表中所有数据项的个数总和
17 unsigned int quicklistCount(quicklist *ql);
18 // 比较两个quicklist结构数据
19 int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
20 // 从节点node中取出LZF压缩编码后的数据
21 size_t quicklistGetLzf(const quicklistNode *node, void **data);

五、quicklist小结
  quicklist将sdlist和ziplist两者的优点结合起来,在时间和空间上做了一个均衡,能较大程度上提高Redis的效率。压入和弹出操作的时间复杂度都很理想。
参考文章:

https://blog.csdn.net/terence1212/article/details/53770882

https://blog.csdn.net/men_wen/article/details/70229375?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.base&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.base

posted @ 2021-07-10 22:12  Mr-xxx  阅读(105)  评论(0编辑  收藏  举报