简介
The ziplist is a specially encoded dually linked list that is designed to be very memory efficient. It stores both strings and integer values,where integers are encoded as actual integers instead of a series of characters. It allows push and pop operations on either side of the list in O(1) time. However, because every operation requires a reallocation of the memory used by the ziplist, the actual complexity is related to the amount of memory used by the ziplist.
数据结构
总体上ziplist由三部分组成ZIPLIST_HEADER 大小为(sizeof(uint32_t)*2+sizeof(uint16_t))。中间存储entry,最后面存储zip_end结束标记
- ZIPLIST OVERALL LAYOUT:
- The general layout of the ziplist is as follows:
- zlbytes 一个uint32_t的数据,用来保存总大小
- zltail 用来保存最后一个entry的偏移
3.zllen 一个uint16_t用来保存entry的数量。所以我们ziplist头的大小是两个uint32_t和一个uint16_t
4.entry编码的存储数据
5.zlend uint8_t保存结束标记 ZIP_END
因为和zipmap一样都是线性保存。所以没有抽象出具体的数据结构
对于ziplist的初始化
#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl))) /*第一个uint32_t是保存的zlbytes*/
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t)))) /*第二个uint32_t保存的zltail*/
#define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))/*两个uint32_t后面的uint_16_t保存的zllen*/
#define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t))
/* Create a new empty ziplist. */
unsigned char *ziplistNew(void) {
unsigned int bytes = ZIPLIST_HEADER_SIZE+1; //heaer + end
unsigned char *zl = zmalloc(bytes);
ZIPLIST_BYTES(zl) = intrev32ifbe(bytes); //设置
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE); //当前没有数据偏移就是header_size 指向end代表空
ZIPLIST_LENGTH(zl) = 0;//长度为0
zl[bytes-1] = ZIP_END;//末尾设置
return zl;
}
了解了ziplist的线性结构,对于初始化来说很好理解
数据编码
typedef struct zlentry {
unsigned int prevrawlensize, prevrawlen;//上一个元素的数据的大小,保存上一个元素长度的长度
unsigned int lensize, len;//保存当前数据块长度的长度,当前数据块的长度
unsigned int headersize;//prevrawlensize + lensize 就是保存前面数据信息和当前数据信息的长度
unsigned char encoding;//编码方式
unsigned char *p;//开始位置
} zlentry;
ziplist有一个数据结构zlentry 这个数据结构并不是ziplist保存entry的数据结构。这个数据结构是通过对一个线性的entry进行解码出来的。他可以体现一个entry里面保存的数据。
prevrawlen:保存它上一个元素的数据大小。我们可以通过p-prevrawlen得到上一个entry的位置
len:保存当前entry的总大小。可以确定自己的边界
headersize:通过prevrawlensize+lensize来得到表示当前header大小
encoding:数据的编码方式,确定后面保存的数据该怎么获取
p:保存自己的开始位置,根据p+headersize和得到我们数据开始的位置
通过这个数据结构再加上我们前面zipmap的经验。我们基本可以猜测一下我们entry的线性结构就是prevrawlensize+prevrawlen+lensize+len+encode,但是我们注意到我们的heaersize里面有没有encoding的空间。所以我们encode的保存方式会放入len里面去,或者是根据encode来确定咱们len的长度
编码类型
既然我们上面注意到encode并没有被单独列出来,所以我们来看下我们具体的encode类型
#define ZIP_STR_MASK 0xc0 /*1100 0000*/
#define ZIP_INT_MASK 0x30 /*0011 000*/
#define ZIP_STR_06B (0 << 6) /*0000 0000*/
#define ZIP_STR_14B (1 << 6) /*0100 0000*/
#define ZIP_STR_32B (2 << 6) /*1000 0000*/
#define ZIP_INT_16B (0xc0 | 0<<4)/*1100 0000*/
#define ZIP_INT_32B (0xc0 | 1<<4)/*1101 0000*/
#define ZIP_INT_64B (0xc0 | 2<<4)/*1110 0000*/
#define ZIP_INT_24B (0xc0 | 3<<4)/*1111 0000*/
#define ZIP_INT_8B 0xfe /*1111 1110*/
/* 4 bit integer immediate encoding */
#define ZIP_INT_IMM_MASK 0x0f /*0000 1111*/
#define ZIP_INT_IMM_MIN 0xf1 /* 1111 0001 */
#define ZIP_INT_IMM_MAX 0xfd /* 1111 1101 */
#define ZIP_INT_IMM_VAL(v) (v & ZIP_INT_IMM_MASK)
我们可以很直观的发现我们的encode后面有个xxB,就是我们位的大小。其实我们就可以发现我们的encode包含了咱们的lensize的长度。所以更新下线性结构为prevrawlensize+prevrawlen+encode+xxbit
我们会发现我们看到了6B 14B这些怪异的定义,一般来说我们的一个字节是8位,但是这里少了两位,那我们就来看下具体的实现。
其实我们细心就可以发现我们ZIP_STR_MASK 位1100 0000,其实意思就是我们的高两位作为了我们的encode类型的存储。
ZIP_STR_06B 位0000 0000 ,他的encode的类型位置就是高两位的00,这个时候我们还剩了6个字节没有用,所以6B的来源就是这里,我们的低六位作为了存储长度。
同理ZIP_STR_14B 0100 0000 我们的类型标致位为01 后面剩余了6个字节,然后在加上一个额外分配的uint8_t空间组成了14B
ZIP_STR_32B 1000 0000 32B 代表四个uint8_t来组成。没有再使用剩余的6字节,这个时候范围已经足够表示了。
我们在看来int的
ZIP_INT_MASK 0011 0000 它使用第三位和第四位来保存类型
ZIP_INT_16B 1100 0000 既00 代表着后面的数据2字节
ZIP_INT_32B 1101 0000 即01 代表后面数据4字节
ZIP_INT_64B 1110 0000 即10 代表后面数据8字节
ZIP_INT_24B 1111 0000 即11 代表后面数据3字节
ZIP_INT_8B 1111 1110 看不出代表为代表后面数据为1字节
这个8B看起来很突兀,不符合前面的标准,而且不符合中间两位的原则。所以我们需要来看一下
我们发现下面的定义/* 4 bit integer immediate encoding */ 其实我们str+int的encode定义中排除zip_int_8B我们会发现,其实只用了前四位,如果不把后四位一起用起来,是不是有点不符合咱们Redis各种节约内存的做法喃。嘿嘿,事实上,它还是真的用起来了。
低四位可以保存的数据为0-15,所以有开发的空间,这时候我们的int还需要一个定义所以会占用1个表示的空间,我们的高四位的字节已经用完,所以低四位不能全为0这样会造成冲突,所以至少低位有一位需要不一样,Redis给出的是最低位+1,所以占用了一个表示空间,全为1111即15的表示会造成和高四位构成一个结束标记,所以再排除,所以我们还剩下0-12的空间给我们用,所以就有了:
ZIP_INT_IMM_MASK 0000 1111
ZIP_INT_IMM_MIN 0000 0001
ZIP_INT_IMM_MAX 0000 1101
ZIP_INT_8B 000 1110
给一个源码的解释:
- The other header field of the entry itself depends on the contents of the
- entry. When the entry is a string, the first 2 bits of this header will hold
- the type of encoding used to store the length of the string, followed by the
- actual length of the string. When the entry is an integer the first 2 bits
- are both set to 1. The following 2 bits are used to specify what kind of
- integer will be stored after this header. An overview of the different
- types and encodings is as follows:
- |00pppppp| - 1 byte
String value with length less than or equal to 63 bytes (6 bits).
- |01pppppp|qqqqqqqq| - 2 bytes
String value with length less than or equal to 16383 bytes (14 bits).
- |10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes
String value with length greater than or equal to 16384 bytes.
- |11000000| - 1 byte
Integer encoded as int16_t (2 bytes).
- |11010000| - 1 byte
Integer encoded as int32_t (4 bytes).
- |11100000| - 1 byte
Integer encoded as int64_t (8 bytes).
- |11110000| - 1 byte
Integer encoded as 24 bit signed (3 bytes).
- |11111110| - 1 byte
Integer encoded as 8 bit signed (1 byte).
- |1111xxxx| - (with xxxx between 0000 and 1101) immediate 4 bit integer.
Unsigned integer from 0 to 12. The encoded value is actually from
1 to 13 because 0000 and 1111 can not be used, so 1 should be
subtracted from the encoded 4 bit value to obtain the right value.
- |11111111| - End of ziplist.
现在我们对entry的表示结构和编码方式以及了解了,后面的操作就是水到渠成了。直接来看Redis对于encode 和 len的操作
/* Extract the encoding from the byte pointed by 'ptr' and set it into
* 'encoding'. */
/*获取encode信息*/
#define ZIP_ENTRY_ENCODING(ptr, encoding) do { \
(encoding) = (ptr[0]); /*第一个字节保存的是encode信息*/\
if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK;/*str encode 使用的是高两位 只要高两位有0 肯定小于 ZIP_STR_MASK*/\
} while(0)
/* Return bytes needed to store integer encoded by 'encoding' */
/*获取对encode是int的长度*/
unsigned int zipIntSize(unsigned char encoding) {
switch(encoding) {
case ZIP_INT_8B: return 1;
case ZIP_INT_16B: return 2;
case ZIP_INT_24B: return 3;
case ZIP_INT_32B: return 4;
case ZIP_INT_64B: return 8;
default: return 0; /* 4 bit immediate */
}
assert(NULL);
return 0;
}
#define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do { \
ZIP_ENTRY_ENCODING((ptr), (encoding));/*获取encode的类型*/ \
if ((encoding) < ZIP_STR_MASK) { /*依次判断就好*/ \
if ((encoding) == ZIP_STR_06B) { \
(lensize) = 1; \
(len) = (ptr)[0] & 0x3f; \
} else if ((encoding) == ZIP_STR_14B) { \
(lensize) = 2; \
(len) = (((ptr)[0] & 0x3f) << 8) | (ptr)[1]; \
} else if (encoding == ZIP_STR_32B) { \
(lensize) = 5; \
(len) = ((ptr)[1] << 24) | \
((ptr)[2] << 16) | \
((ptr)[3] << 8) | \
((ptr)[4]); \
} else { \
assert(NULL); \
} \
} else { \
(lensize) = 1; \
(len) = zipIntSize(encoding); \
} \
} while(0);
插入
现在我们把编码方面理清楚了之后,直接来看插入操作。
首先我们的ziplist本质上是一个双向链表,保存了next信息和prev信息,其实next信息是利用内存的连续性来保存的,不需要我们进行额外的操作。prev信息是通过我们前面说的prevrawlen来存储的这个字段的编码方式和zipmap的存储长度方式一致就在这里不重复说了。
插入本身除了把自己进行编码放入内存之后,还有一个重要的操作就是维护上下文的链表关系,由于内存的连续,所以我们不需要维护next,但是我们需要维护prev.
我们的插入操作是在某个节点后插入,这时候我们是先要拿到一个节点这时候其实我们就有节点的信息(没有的时候属于空间的,也算是有节点信息吧),我们在插入的时候保存这个信息就足够了。直接来看插入代码
* Insert item at "p". */
unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen; //list的大小
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)) {//是否能够转换成int存储
/* 'encoding' is set to the appropriate integer encoding */
reqlen = zipIntSize(encoding);
} else {
/* 'encoding' is untouched, however zipEncodeLength 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 += zipPrevEncodeLength(NULL,prevlen);//加上保存prevlen所需要的空间
reqlen += zipEncodeLength(NULL,encoding,slen);//加上保存encode需要的空间
/* 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;//如果不为空,我需要知道两个length之间的差距(因为我们插入在前面所以原来的需要更改prevlen)
//nextdiff只0 4 -4 //0代表两个需要大小一样 4 代表新的保存空间需要更大 -4 代表原来保存的位置太大了
//reqlen
if (nextdiff == -4 && reqlen < 4) {
/*
在一般的情况下不会触发 nextdiff == -4 reqlen < 4 的情况 因为如果我们的reqlen中prvelen 来源于p所以 prvlen=5 relen至少也是5
但是喃 在updatecase里面当节点变化的时候 当前节点保存上一个节点的长度的空间过大的时候 我们不会选择缩小我们保存prvlen的空间 所以照成了这个情况
继续不缩小空间 还是用大的写
*/
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 */
//p+reqlen 如果新数据放入后的尾部
//p+diff diff=0时很明显
//diff = -4 说明我们原来保存的len太大了 我们的实际存储肯定是第六位开始的(前面五个字节为len)原来前四个保存长度的没拷贝
//diff = 4 说明我们原来保存的len太小了 我们多拷贝了四个字节走 我们的实际数据拷贝么有问题
//实际上就是留出后面保存prevlen的长度
memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);
/* Encode this entry's raw length in the next entry. */
if (forcelarge)
zipPrevEncodeLengthForceLarge(p+reqlen,reqlen);
else
zipPrevEncodeLength(p+reqlen,reqlen);//我们的move的时候把存放len的位置留出来了
/* Update offset for tail */
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);//更新新尾节点的位置 没加diff的原因在下面
/* 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) {//找到下一个节点 我们这个节点后面没有了的话 那就是代表被插入的节点就是尾节点了,diff的变化对他没影响,否则的话就需要更新 可以加上 if(nextdiff !=0 )再来做操作
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);//设置 tail的偏移
}
/* 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) {//如果 不等于0 我们的节点会被改变 主要是存储prvelen改变了 我们需要对后面的进行循环改变 这部分代码可以放入到 p[0] !ZIP_END里面去 最后再来汇总 因为 nextdiff产生的条件是p[0] !=ZIP_END
offset = p-zl;
zl = __ziplistCascadeUpdate(zl,p+reqlen);//更新
p = zl+offset;
}
/* Write the entry */
p += zipPrevEncodeLength(p,prevlen);//保存prevlen的大小
p += zipEncodeLength(p,encoding,slen);//保存自己的大小
if (ZIP_IS_STR(encoding)) {//保存数据
memcpy(p,s,slen);
} else {
zipSaveInteger(p,value,encoding);
}
ZIPLIST_INCR_LENGTH(zl,1);
return zl;
}
unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {
size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), rawlen, rawlensize;
size_t offset, noffset, extra;
unsigned char *np;
zlentry cur, next;
while (p[0] != ZIP_END) {//循环结束条件到达终点
zipEntry(p, &cur);//获取字节的情况
rawlen = cur.headersize + cur.len;
rawlensize = zipPrevEncodeLength(NULL,rawlen);//获取自己需要的大小
/* Abort if there is no next entry. */
if (p[rawlen] == ZIP_END) break;//自己后面没人了 不用改变了
zipEntry(p+rawlen, &next);//获取字节后面大小的信息
/* Abort when "prevlen" has not changed. */
if (next.prevrawlen == rawlen) break;//大小没变
if (next.prevrawlensize < rawlensize) {//存储空间不够了
/* The "prevlen" field of "next" needs more bytes to hold
* the raw length of "cur". */
offset = p-zl;//保存偏移
extra = rawlensize-next.prevrawlensize;//diff
zl = ziplistResize(zl,curlen+extra);//resize
p = zl+offset;//还原p
/* Current pointer and offset for next element. */
np = p+rawlen;//下一个节点的位置
noffset = np-zl;//下一个节点对于的偏移
/* Update tail offset when next element is not the tail element. */
if ((zl+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) != np) {//如果后面还有节点 就必须要更新tail的位置信息
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra);
}
/* Move the tail to the back. */
memmove(np+rawlensize,
np+next.prevrawlensize,
curlen-noffset-next.prevrawlensize-1);//数据移动
zipPrevEncodeLength(np,rawlen);//编码len
/* Advance the cursor */
p += rawlen;//到下一个位置
curlen += extra;//len变成了
} else {
if (next.prevrawlensize > rawlensize) {//多了
/* This would result in shrinking, which we want to avoid.
* So, set "rawlen" in the available bytes. */
zipPrevEncodeLengthForceLarge(p+rawlen,rawlen);//懒些 用大的保存小的减少操作
} else {
zipPrevEncodeLength(p+rawlen,rawlen);//大小合适 保存下收工
}
/* Stop here, as the raw length of "next" has not changed. */
break;
}
}
return zl;
}
/* Delete "num" entries, starting at "p". Returns pointer to the ziplist. */
unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) {
unsigned int i, totlen, deleted = 0;
size_t offset;
int nextdiff = 0;
zlentry first, tail;
zipEntry(p, &first);
for (i = 0; p[0] != ZIP_END && i < num; i++) {
p += zipRawEntryLength(p);
deleted++;
}
totlen = p-first.p;
if (totlen > 0) {
if (p[0] != ZIP_END) {
/* Storing `prevrawlen` in this entry may increase or decrease the
* number of bytes required compare to the current `prevrawlen`.
* There always is room to store this, because it was previously
* stored by an entry that is now being deleted. */
nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);
p -= nextdiff;
zipPrevEncodeLength(p,first.prevrawlen);
/* Update offset for tail */
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))-totlen);
/* 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, &tail);
if (p[tail.headersize+tail.len] != ZIP_END) {
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
}
/* Move tail to the front of the ziplist */
memmove(first.p,p,
intrev32ifbe(ZIPLIST_BYTES(zl))-(p-zl)-1);
} else {
/* The entire tail was deleted. No need to move memory. */
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe((first.p-zl)-first.prevrawlen);
}
/* Resize and update length */
offset = first.p-zl;
zl = ziplistResize(zl, intrev32ifbe(ZIPLIST_BYTES(zl))-totlen+nextdiff);
ZIPLIST_INCR_LENGTH(zl,-deleted);
p = zl+offset;
/* 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)
zl = __ziplistCascadeUpdate(zl,p);
}
return zl;
}
unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {
size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), rawlen, rawlensize;
size_t offset, noffset, extra;
unsigned char *np;
zlentry cur, next;
while (p[0] != ZIP_END) {//循环结束条件到达终点
zipEntry(p, &cur);//获取字节的情况
rawlen = cur.headersize + cur.len;
rawlensize = zipPrevEncodeLength(NULL,rawlen);//获取自己需要的大小
/* Abort if there is no next entry. */
if (p[rawlen] == ZIP_END) break;//自己后面没人了 不用改变了
zipEntry(p+rawlen, &next);//获取字节后面大小的信息
/* Abort when "prevlen" has not changed. */
if (next.prevrawlen == rawlen) break;//大小没变
if (next.prevrawlensize < rawlensize) {//存储空间不够了
/* The "prevlen" field of "next" needs more bytes to hold
* the raw length of "cur". */
offset = p-zl;//保存偏移
extra = rawlensize-next.prevrawlensize;//diff
zl = ziplistResize(zl,curlen+extra);//resize
p = zl+offset;//还原p
/* Current pointer and offset for next element. */
np = p+rawlen;//下一个节点的位置
noffset = np-zl;//下一个节点对于的偏移
/* Update tail offset when next element is not the tail element. */
if ((zl+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) != np) {//如果后面还有节点 就必须要更新tail的位置信息
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra);
}
/* Move the tail to the back. */
memmove(np+rawlensize,
np+next.prevrawlensize,
curlen-noffset-next.prevrawlensize-1);//数据移动
zipPrevEncodeLength(np,rawlen);//编码len
/* Advance the cursor */
p += rawlen;//到下一个位置
curlen += extra;//len变成了
} else {
if (next.prevrawlensize > rawlensize) {//多了
/* This would result in shrinking, which we want to avoid.
* So, set "rawlen" in the available bytes. */
zipPrevEncodeLengthForceLarge(p+rawlen,rawlen);//懒些 用大的保存小的减少操作
} else {
zipPrevEncodeLength(p+rawlen,rawlen);//大小合适 保存下收工
}
/* Stop here, as the raw length of "next" has not changed. */
break;
}
}
return zl;
}
删除
ziplist的删除操作特别简洁,因为他是使用边界来保存next,所以直接把自身内容删除就好了.
unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) {
unsigned int i, totlen, deleted = 0;
size_t offset;
int nextdiff = 0;
zlentry first, tail;
zipEntry(p, &first);
for (i = 0; p[0] != ZIP_END && i < num; i++) {//万一你的num写多了喃
p += zipRawEntryLength(p); //偏移到下一个节点
deleted++;
}
totlen = p-first.p;
if (totlen > 0) { //看下到底偏移没
if (p[0] != ZIP_END) {//后面还有节点
/* Storing `prevrawlen` in this entry may increase or decrease the
* number of bytes required compare to the current `prevrawlen`.
* There always is room to store this, because it was previously
* stored by an entry that is now being deleted. */
nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);//后面的节点需要去保存删除的第一个节点的prvlen
p -= nextdiff; //多了的话就删了 少了的话就哪要删除的补上
zipPrevEncodeLength(p,first.prevrawlen);//设置len
/* Update offset for tail */
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))-totlen);//更新tail的位置
/* 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, &tail);
if (p[tail.headersize+tail.len] != ZIP_END) {//如果后面还有节点需要再加上nextdiff m
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
}
/* Move tail to the front of the ziplist */
memmove(first.p,p,
intrev32ifbe(ZIPLIST_BYTES(zl))-(p-zl)-1);//把后面的数据移上来
} else {
/* The entire tail was deleted. No need to move memory. */
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe((first.p-zl)-first.prevrawlen);//没有节点就直接移动数据
}
/* Resize and update length */
offset = first.p-zl;//保存偏移
zl = ziplistResize(zl, intrev32ifbe(ZIPLIST_BYTES(zl))-totlen+nextdiff);//回收空间
ZIPLIST_INCR_LENGTH(zl,-deleted);//改变len
p = zl+offset;//找到p
/* 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)//和inset一样 如果有大小变化后面需要更新
zl = __ziplistCascadeUpdate(zl,p);
}
return zl;
}
总结
ziplist 作为一个复杂的双向list,他本身所需要的自我开销特别的少,就只有prevlen encode 其他保存的都是我们的数据信息。所以再节约内存上会特别好。但是由于看不出具体的数据结构,会导致在操作上变得特别的复杂