1.介绍
Redis在3.2版本之前List的底层编码是ZipList和LinkedList实现的
在3.2版本之后,重新引入了QuickList的数据结构,列表的底层都是QuickList实现
当List对象中元素的长度比较小或者数量比较少的时候,采用ZipList来存储
当List对象中元素的长度比较大或者数量比较多的时候,采用LinkList来存储
这两种存储方式的优缺点
LinkedList便于在表的两端进行Push和Pop操作,在插入节点复杂度很低,但是它的内存开销很大,首先,它在每个节点上除了要保存数据之外,还要额外保存两个指针。
其次;双向链表的各个节点时单独的内存块,地址不连续,节点多了容易产生内存碎片
ZipList存储在一段连续的内存上,所以存储效率很高,但是它不利于修改操作,插入和删除操作需要频繁的申请释放内存。
特别是当ZipList长度很长的时候,一次Realloc可能导致大批量的数据拷贝
QuickList可以看作一个双向列表,但是列表的每个节点都是一个ziplist,
其实就是linkedlist和ziplist的结合。quicklist中的每个节点ziplist都能够存储多个数据元素。
2.示意图
3.源码实现
typedef struct quicklist { quicklistNode *head; // 指向quicklist的头部 quicklistNode *tail; // 指向quicklist的尾部 unsigned long count; // 列表中所有数据项的个数总和 unsigned int len; // quicklist节点的个数,即ziplist的个数 int fill : 16; // ziplist大小限定,由list-max-ziplist-size给定 unsigned int compress : 16; // 节点压缩深度设置,由list-compress-depth给定 } quicklist;
count用来统计所有数据项的个数总和,len用来统计quicklist的节点个数, 因为每个节点ziplist都能存储多个数据项,所以有了这两个统计值。
typedef struct quicklistNode { struct quicklistNode *prev; // 指向上一个ziplist节点 struct quicklistNode *next; // 指向下一个ziplist节点 unsigned char *zl; // 数据指针,如果没有被压缩,就指向ziplist结构,反之指向quicklistLZF结构 unsigned int sz; // 表示指向ziplist结构的总长度(内存占用长度) unsigned int count : 16; // 表示ziplist中的数据项个数 unsigned int encoding : 2; // 编码方式,1--ziplist,2--quicklistLZF unsigned int container : 2; // 预留字段,存放数据的方式,1--NONE,2--ziplist unsigned int recompress : 1; // 解压标记,当查看一个被压缩的数据时,需要暂时解压,标记此参数为1,之后再重新进行压缩 unsigned int attempted_compress : 1; // 测试相关 unsigned int extra : 10; // 扩展字段,暂时没用 } quicklistNode;
QuickList的迭代器结构,指向迭代器节点元素
// quicklist的迭代器结构 typedef struct quicklistIter { const quicklist *quicklist; // 指向所在quicklist的指针 quicklistNode *current; // 指向当前节点的指针 unsigned char *zi; // 指向当前节点的ziplist long offset; // 当前ziplist中的偏移地址 int direction; // 迭代器的方向 } quicklistIter; // 表示quicklist节点中ziplist里的一个节点结构 typedef struct quicklistEntry { const quicklist *quicklist; // 指向所在quicklist的指针 quicklistNode *node; // 指向当前节点的指针 unsigned char *zi; // 指向当前节点的ziplist unsigned char *value; // 当前指向的ziplist中的节点的字符串值 long long longval; // 当前指向的ziplist中的节点的整型值 unsigned int sz; // 当前指向的ziplist中的节点的字节大小 int offset; // 当前指向的ziplist中的节点相对于ziplist的偏移量 } quicklistEntry;
创建QuickList
fill:
-5: 每个quicklist节点上的ziplist大小不能超过64 Kb。(注:1kb => 1024 bytes)
-4: 每个quicklist节点上的ziplist大小不能超过32 Kb。
-3: 每个quicklist节点上的ziplist大小不能超过16 Kb。
-2: 每个quicklist节点上的ziplist大小不能超过8 Kb。(-2是Redis给出的默认值)
-1: 每个quicklist节点上的ziplist大小不能超过4 Kb。
compress:
0 特殊值,表示不压缩
1 表示quicklist两端各有一个节点不压缩,中间的节点压缩
2 表示quicklist两端各有两个节点不压缩,中间的节点压缩
3 表示quicklist两端各有三个节点不压缩,中间的节点压缩
quicklist *quicklistCreate(void) { struct quicklist *quicklist; // 声明指针 quicklist = zmalloc(sizeof(*quicklist)); // 分配内存 quicklist->head = quicklist->tail = NULL; // 设定头尾指针 quicklist->len = 0; // 设定长度 quicklist->count = 0; // 设定数据项总和 quicklist->compress = 0; // 设定压缩深度 quicklist->fill = -2; // 设定ziplist大小限定 return quicklist; }
创建QuickListNode
REDIS_STATIC quicklistNode *quicklistCreateNode(void) { quicklistNode *node; node = zmalloc(sizeof(*node)); // 申请内存 node->zl = NULL; // 初始化指向ziplist的指针 node->count = 0; // 初始化数据项个数 node->sz = 0; // 初始化ziplist大小 node->next = node->prev = NULL; // 初始化prev和next指针 node->encoding = QUICKLIST_NODE_ENCODING_RAW; // 初始化节点编码方式 node->container = QUICKLIST_NODE_CONTAINER_ZIPLIST; // 初始化存放数据的方式 node->recompress = 0; // 初始化再压缩标记 return node; }