ZipList压缩列表
ziplist是一个经过特殊编码的双向链表,它的设计目标就是为了提高存储效率。ziplist可以用于存储字符串或整数,其中整数是按真正的二进制表示进行编码的,而不是编码成字符串序列。它能以O(1)的时间复杂度在表的两端提供push和pop操作。
数据结构
属性 | 说明 |
---|---|
zlbytes | 记录整个压缩列表占用的内存字节数;在对压缩列表进行内存重分配,或者计算zlend的位置时使用 |
zltail | 记录压缩列表表尾节点距离头结点有多少字节,无须遍历就能够确定表尾节点。 |
zllen | 记录压缩列表的节点数量。 |
entry | 压缩列表的节点 |
zlend | 标记压缩列表末端 |
previous_entry_length | 记录压缩列表中前一个节点的长度,1或者5字节;可以通过指针运算计算出前一个节点的起始位置 |
encoding | 记录content属性所保存数据的类型以及长度 |
content | 保存节点的值,值得类型和长度由节点的encoding属性决定 |
3.2之前
/* Create a new empty ziplist. */
unsigned char *ziplistNew(void) {
// 表头加末端大小
unsigned int bytes = ZIPLIST_HEADER_SIZE+1;
// 为表头和表末端分配空间
unsigned char *zl = zmalloc(bytes);
// 初始化表属性
ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);
ZIPLIST_LENGTH(zl) = 0;
// 设置表末端
zl[bytes-1] = ZIP_END;
return zl;
}
typedef struct zlentry {
// 前一节点长度信息的长度
unsigned int prevrawlensize;
// 前一节点长度
unsigned int prevrawlen;
// 当前节点长度信息长度
unsigned int lensize;
// 当前节点长度
unsigned int len;
// 当前节点头部信息长度
unsigned int headersize;
// 当前节点数据编码
unsigned char encoding;
unsigned char *p;
} zlentry;
void zipEntry(unsigned char *p, zlentry *e) {
// 前一节点长度信息解析
ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen);
// 当前节点数据长度与编码信息解析
ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len);
e->headersize = e->prevrawlensize + e->lensize;
e->p = p;
}
问题
连锁更新
当在entry1
之前添加一个节点,此时如果entry1
的 previous_entry_length 只记录 1 个长度,然而新节点如果大于254字节那么新节点的长度要用5字节长的空间来保存。entry1
所占的空间就需要往后移动,重新分配空间。扩展entry1
也可能导致需要扩展entry2
…相互影响导致连锁更新
但是这种情况不常见,一半不影响性能
重点
- 压缩列表是一种为节约内存而开发的顺序性数据结构。
- 压缩列表被用作列表键和哈希键的底层实现之一。
每当有新的键值对要加入到哈希对象时, 程序会先将保存了键的压缩列表节点推入到压缩列表表尾, 然后再将保存了值的压缩列表节点推入到压缩列表表尾。查找并非 - 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
- 新增或者删除节点可能导致连锁更新。