zlbytes:4字节,记录整个压缩列表占用的内存字节数
zltail:4字节,记录压缩列表表尾节点距离压缩列表的起始地址有多少字节。即entryN在ziplist中的偏移
zllen:2字节,记录了压缩列表包含的节点数,当这个属性的值小于UINT16_MAX时,这个属性的值就是压缩列表包含节点的数量 ,当这个值等于UINT16_MAX时,节点的真实数量需要遍历整个压缩列表才能计算得出。
zlend:1字节,特殊值0xff,用于标记压缩列表的末端。
压缩列表节点可以保存一个字节串或者一个整数。
每个压缩节点的组成
previous_entry_length以字节为单位,记录了压缩列表中前一个节点的长度。取值可以是1字节或者5字节。
如果前一节点的长度小于254字节,那么previous_entry_length长度为1字节,前一节点的长度就保存在这一个字节里。
如果前一节点的长度大于等于254字节,那么previous_entry_length长度为5字节,其中第一字节设置为0xFE,之后的四字节用于保存前一节点的长度。
一字节、两字节或者五字节长,值 的最高位为00,01或者10的是字节数组编码,这种编码表示节点的content属性保存着字节数组,数组的长度由编码除去最高两位之后的其他位记录。
一字节长,值的最高位以11开头的是整数编码,这种编码表示节点的content属性保存的是整数值,整数值的类型和长度由编码除去最高两位之后的其他位记录。
encoding记录节点的content属性所保存数据的类型以及长度
字符串支持的编码形式有
长度 | 编码 | 说明 |
<=64 | |00pppppp| |
pppppp表示字符串长度 |
<=16383 | |01pppppp|qqqqqqqq| |
p表示字符串长度,大端字符串存储 |
>=16384 | |10000000|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| |
|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt|表示字符口中长度,大端字符序存储 |
整数支持的编码形式有
长度 | 编码 | 说明 |
2字节 | |11000000| |
总共占用3字节(encoding+content) |
4字节 | |11010000| |
总共占用5字节 |
8字节 | |11100000| |
总共占用9字节 |
3字节 | |11110000| |
总共占用4字节 |
1字节 | |11111110| |
总共占用2字节 |
0-12 | |1111xxxx| |
实际存储是的[1-13] |
content保存节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定。
createZiplistObject来创建ziplist
robj *createZiplistObject(void) {
unsigned char *zl = ziplistNew();
robj *o = createObject(OBJ_LIST,zl);
o->encoding = OBJ_ENCODING_ZIPLIST;
return o;
}
ziplistPush来添加entry,内部是调用__ziplistInsert,在位置p前插入entry(s, slen),会更新p的prelen,ziplist的zltail,zlbytes, zllen
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) {
unsigned char *p;
p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);
return __ziplistInsert(zl,p,s,slen);
}
unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen;
unsigned int prevlensize, prevlen = 0;
size_t offset;
int nextdiff = 0;
unsigned char encoding = 0;
long long value = 123456789; /* initialized to avoid warning. Using a value
that is easy to see if for some reason
we use it uninitialized. */
zlentry tail;
/* Find out prevlen for the entry that is inserted. */
if (p[0] != ZIP_END) {
ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
} else {
unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
if (ptail[0] != ZIP_END) {
prevlen = zipRawEntryLength(ptail);
}
}
/* See if the entry can be encoded */
if (zipTryEncoding(s,slen,&value,&encoding)) {
/* 'encoding' is set to the appropriate integer encoding */
reqlen = zipIntSize(encoding);
} else {
/* 'encoding' is untouched, however zipStoreEntryEncoding will use the
* string length to figure out how to encode it. */
reqlen = slen;
}
/* We need space for both the length of the previous entry and
* the length of the payload. */
reqlen += zipStorePrevEntryLength(NULL,prevlen);
reqlen += zipStoreEntryEncoding(NULL,encoding,slen);
/* When the insert position is not equal to the tail, we need to
* make sure that the next entry can hold this entry's length in
* its prevlen field. */
int forcelarge = 0;
nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
if (nextdiff == -4 && reqlen < 4) {
nextdiff = 0;
forcelarge = 1;
}
/* Store offset because a realloc may change the address of zl. */
offset = p-zl;
zl = ziplistResize(zl,curlen+reqlen+nextdiff);
p = zl+offset;
/* Apply memory move when necessary and update tail offset. */
if (p[0] != ZIP_END) {
/* Subtract one because of the ZIP_END bytes */
memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);
/* Encode this entry's raw length in the next entry. */
if (forcelarge)
zipStorePrevEntryLengthLarge(p+reqlen,reqlen);
else
zipStorePrevEntryLength(p+reqlen,reqlen);
/* Update offset for tail */
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);
/* When the tail contains more than one entry, we need to take
* "nextdiff" in account as well. Otherwise, a change in the
* size of prevlen doesn't have an effect on the *tail* offset. */
zipEntry(p+reqlen, &tail);
if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
}
} else {
/* This element will be the new tail. */
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);
}
/* When nextdiff != 0, the raw length of the next entry has changed, so
* we need to cascade the update throughout the ziplist */
if (nextdiff != 0) {
offset = p-zl;
zl = __ziplistCascadeUpdate(zl,p+reqlen);
p = zl+offset;
}
/* Write the entry */
p += zipStorePrevEntryLength(p,prevlen);
p += zipStoreEntryEncoding(p,encoding,slen);
if (ZIP_IS_STR(encoding)) {
memcpy(p,s,slen);
} else {
zipSaveInteger(p,value,encoding);
}
ZIPLIST_INCR_LENGTH(zl,1);
return zl;
}
redis中的hash当使用ziplist来存储key,value时,其内部entry列表顺序为key1,value1,..., keyn,valuen