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
本文来自博客园,作者:Mr-xxx,转载请注明原文链接:https://www.cnblogs.com/MrLiuZF/p/14995102.html