Redis中压缩链表ziplist数据结构与API相关文件是:ziplist.h, ziplist.c, t_zset.c。

一、ziplist的构成

<zlbytes><zltail><zllen><entry><entry><zlend>

<zlbytes>是一个4字节无符号整数,用来存储整个ziplist占用的字节数;

<zltail>是一个4字节无符号整数,用来存储ziplist最后一个节点的相对于ziplist首地址偏移量;

<zllen>是一个2字节无符号整数,存储ziplist中节点的数目,最大值为(2^16 - 2),当zllen大于最大值时,需要遍历整个ziplist才能获取ziplist节点的数目;

<entry>ziplist存储的节点,各个节点的字节数根据内容而定;

<zlend>是一个1字节无符号整数,值为255(11111111),作为ziplist的结尾符

redis sadd复杂度 redis的时间复杂度_redis sadd复杂度

二、ziplist宏模块


宏名称

作用

ZIPLIST_BYTES(zl)

获取zlbytes值

ZIPLIST_TAIL_OFFSET(zl)

获取zltail值

ZIPLIST_LENGTH(zl)

获取zllen值

ZIPLIST_HEADER_SIZE

获取ziplist header的长度,固定为10个字节

ZIPLIST_ENTRY_HEAD(zl)

获取ziplist第一个entry的首地址

ZIPLIST_ENTRY_TAIL(zl)

获取ziplist最后一个entry的首地址

ZIPLIST_ENTRY_END(zl)

获取ziplist的末端,即zlend首地址


 

四、entry结构解析

prev_entry_bytes_length:

表示上个节点所占的字节数,即上个节点的长度,如果需要跳到上个节点,而已知道当前节点的首地址p,上个节点的首地址prev = p-prev_entry_bytes_length

根据编码方式的不同,prev_entry_bytes_length可能占1 bytes或5 bytes:

    1 bytes:如果上个节点的长度小于254,那么就只需要1个字节;

    5 bytes:如果上个节点的长度大于等于254,那么就将第一个字节设为254(1111 1110),然后接下来的4个字节保存实际的长度值;

encoding与length:

ziplist的编码类型分为字符串、整数
encoding的前两个比特位用来判断编码类型是字符串或整数:

            00, 01, 10表示contents中保存着字符串
            11表示contents中保存着整数

        字符串的具体编码方式:(_:预留,a:实际的二进制数)

   

编码

编码长度

contents中的值

00aaaaaa

1 bytes

长度[0,63]个字节的字符串

01aaaaaa bbbbbbbb

2 bytes

长度[64,16383]个字节的字符串

01______ aaaaaaaa bbbbbbbb cccccccc dddddddd

5 bytes

长度[16384,2^32-1]个字节的字符串

 

       11开头的整型编码方式:

编码

编码长度

contents中的值

1100 0000

1 bytes

int16_t类型整数

1101 0000

1 bytes

int32_t类型整数

1110 0000

1 bytes

int64_t类型整数

1111 0000

1 bytes

24 bit 有符号整数

1111 1110

1 bytes

8 bit 有符号整数

1111 xxxx

1 bytes

4 bit 无符号整数,[0,12]

解释一下1111 xxxx编码:1111 xxxx 首先最小值应该是0001(0000已经被占用),最大值应该是1101(1110与111 1均已经被占用),因此,可被编码的值实际上只能是 1 至 13,由于还需要减1,所以实际只能编码[0,12],至于减1的理由,我的理解是方便编码0。


五、zlentry数据结构



[cpp]  view plain copy print ?


1. typedef struct zlentry {  
2. //前一个节点长度的存储所占的字节数,上个节点占用的长度  
3. int prevrawlensize, prevrawlen;  
4. //当前节点长度的存储所占的字节数,当前节点占用的长度  
5. int lensize, len;  
6. int headersize;//当前节点的头部大小  
7. char encoding;//当前链表结点长度(即字段len)使用的编码类型  
8. char *p; //指向当前结点起始位置的指针  
9. } zlentry;

zlentry结构体就是用来存储当前节点的相关信息的,这些信息需要解析得到,解析的代码如下:



[cpp]  view plain copy print ?


1. /判断是否为字符串编码  
2. #define ZIP_ENTRY_ENCODING(ptr, encoding) do {  \  
3.     (encoding) = (ptr[0]); \  
4. if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; \  
5. } while(0)  
6.   
7. //返回 encoding 指定的整数编码方式所需的长度  
8. static unsigned int zipIntSize(unsigned char encoding) {  
9. switch(encoding) {  
10. case ZIP_INT_8B:  return 1;  
11. case ZIP_INT_16B: return 2;  
12. case ZIP_INT_24B: return 3;  
13. case ZIP_INT_32B: return 4;  
14. case ZIP_INT_64B: return 8;  
15. default: return 0; /* 4 bit immediate */  
16.     }  
17.     assert(NULL);  
18. return 0;  
19. }  
20.   
21. //从 ptr 指针中取出节点的编码、保存节点长度所需的字节数、以及节点的长度  
22. //节点保存的类型分字符串与整型  
23. #define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do {                    \  
24.     ZIP_ENTRY_ENCODING((ptr), (encoding));                                     \  
25. if ((encoding) < ZIP_STR_MASK) {//字符串编码                               \  
26. if ((encoding) == ZIP_STR_06B) {                                       \  
27.             (lensize) = 1;                                                     \  
28.             (len) = (ptr)[0] & 0x3f;                                           \  
29. else if ((encoding) == ZIP_STR_14B) {                                \  
30.             (lensize) = 2;                                                     \  
31.             (len) = (((ptr)[0] & 0x3f) << 8) | (ptr)[1];                       \  
32. else if (encoding == ZIP_STR_32B) {                                  \  
33.             (lensize) = 5;                                                     \  
34.             (len) = ((ptr)[1] << 24) |                                         \  
35.                     ((ptr)[2] << 16) |                                         \  
36.                     ((ptr)[3] <<  8) |                                         \  
37.                     ((ptr)[4]);                                                \  
38. else {                                                               \  
39.             assert(NULL);                                                      \  
40.         }                                                                      \  
41. else { //整型编码                                                        \  
42.         (lensize) = 1;                                                         \  
43.         (len) = zipIntSize(encoding);                                          \  
44.     }                                                                          \  
45. } while(0);  
46.   
47. //从指针 ptr 中取出保存前一个节点的长度所需的字节数  
48. //小于254用1个字节,否则用5个字节  
49. #define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do {                          \  
50. if ((ptr)[0] < ZIP_BIGLEN) {                                               \  
51.         (prevlensize) = 1;                                                     \  
52. else {                                                                   \  
53.         (prevlensize) = 5;                                                     \  
54.     }                                                                          \  
55. } while(0);  
56.   
57. //从指针 ptr 中取出前一个节点的长度  
58. //如果保存前一个节点长度只需要1个字节,prevlensize = 1,那么直接得到前一个节点的长度值prevlen  
59. //否则prevlensize后四个字节表示前一个节点的长度  
60. #define ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen) do {                     \  
61.     ZIP_DECODE_PREVLENSIZE(ptr, prevlensize);                                  \  
62. if ((prevlensize) == 1) {                                                  \  
63.         (prevlen) = (ptr)[0];                                                  \  
64. else if ((prevlensize) == 5) {                                           \  
65. sizeof((prevlensize)) == 4);                                    \  
66. char*)(ptr)) + 1, 4);                             \  
67.         memrev32ifbe(&prevlen);                                                \  
68.     }                                                                          \  
69. } while(0);  
70.   
71. //从指针 p 中提取出节点的各个属性,并将属性保存到 zlentry 结构,然后返回  
72. static zlentry zipEntry(unsigned char *p) {  
73.     zlentry e;  
74.     ZIP_DECODE_PREVLEN(p, e.prevrawlensize, e.prevrawlen);  
75.     ZIP_DECODE_LENGTH(p + e.prevrawlensize, e.encoding, e.lensize, e.len);  
76.     e.headersize = e.prevrawlensize + e.lensize;  
77.     e.p = p;  
78. return e;  
79. }



六、ziplist的主要函数列表



函数名

作用

复杂度

ziplistNew

创建一个新的ziplist

O(1)

ziplistResize

重新调整ziplist的大小

O(1)

__ziplistCascadeUpdate

级联更新ziplist中entry的大小

O(N*M)

__ziplistDelete

删除指定位置开始的N个节点

O(N*M)

__ziplistInsert

在指定位置之前插入一个节点

O(N*M)

ziplistIndex

获取第N个节点的首地址

O(N)

ziplistNext

获取当前节点的下一个节点首地址

O(1)

ziplistPrev

获取当前节点的前一个节点首地址

O(1)

ziplistGet

获取给定节点的数据

O(1)

ziplistFind

找到ziplist中包含给定数据的节点

O(N)

ziplistLen

获取ziplist中节点的数目

O(N)

ziplistBlobLen

以字节为单位,返回ziplist占用的内存大小

O(1)



七、另外三个简单而重要的函数




[cpp]  view plain copy print ?


1. //p:当前节点首地址,len:上一个节点的长度值  
2. //如果p==NULL,则返回编码len需要多少个字节,否则将该len存储在当前节点的prev_entry_bytes_length区域  
3. static unsigned int zipPrevEncodeLength(unsigned char *p, unsigned int len) {  
4. if (p == NULL) {  
5. return (len < ZIP_BIGLEN) ? 1 : sizeof(len)+1;  
6. else {  
7. if (len < ZIP_BIGLEN) {  
8.             p[0] = len;  
9. return 1;  
10. else {  
11.             p[0] = ZIP_BIGLEN;  
12. sizeof(len));  
13.             memrev32ifbe(p+1);  
14. return 1+sizeof(len);  
15.         }  
16.     }  
17. }


[cpp]  view plain copy print ?


1. //计算出节点所占的总字节数  
2. static unsigned int zipRawEntryLength(unsigned char *p) {  
3. int prevlensize, encoding, lensize, len;  
4. //得到编码前一个节点长度的字节数  
5. //得到存储当前节点的长度的字节数,当前节点数据的长度  
6. //注意保存字符串与整数之间的差别  
7.     ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);  
8. return prevlensize + lensize + len;  
9. }


[cpp]  view plain copy print ?


1. /* Return the difference in number of bytes needed to store the length of the
2.  * previous element 'len', in the entry pointed to by 'p'. */  
3. //计算需要存储len所需字节数与当前节点p的prev_entry_bytes_length的差值  
4. static int zipPrevLenByteDiff(unsigned char *p, unsigned int len) {  
5. int prevlensize;  
6.     ZIP_DECODE_PREVLENSIZE(p, prevlensize);  
7. return zipPrevEncodeLength(NULL, len) - prevlensize;  
8. }



八、__ziplistCascadeUpdate、__ziplistDelete、__ziplistInsert函数详解


注;以下注释与代码中的一些字段请参照zlentry数据结构中的定义,这三个函数是ziplist一切操作的核心,尤其是在memmove进行数据移动的时候更需要多思考,在阅读下面的代码之前先阅读ziplistResize函数,而且需要对C语言中的realloc重新分配内存函数需要有一定的了解。



[cpp]  view plain copy print ?

1. /**
2.  * 当将一个新节点添加到某个节点之前的时候,如果原节点的prevlen不足以保存新节点的长度,
3.  * 那么就需要对原节点的空间进行扩展(从 1 字节扩展到 5 字节)。
4.  *
5.  * 但是,当对原节点进行扩展之后,原节点的下一个节点的 prevlen 可能出现空间不足,
6.  * 这种情况在多个连续节点的长度都接近 ZIP_BIGLEN 时可能发生。
7.  *
8.  * 这个函数就用于处理这种连续扩展动作。
9.  *
10.  * 因为节点的长度变小而引起的连续缩小也是可能出现的,不过,为了避免扩展-缩小-扩展-缩小这样的情况反复出现(flapping,抖动),
11.  * 我们不处理这种情况,而是任由 prevlen 比所需的长度更长
12.  *
13.  * 复杂度:O(N^2)
14.  *
15.  * 返回值:更新后的 ziplist
16.  * zl: ziplist首地址,p:需要扩展prevlensize的节点首地址
17.  */  
18. static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {  
19. size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), rawlen, rawlensize;  
20. size_t offset, noffset, extra;  
21. char *np;  
22.     zlentry cur, next;  
23.   
24. while (p[0] != ZIP_END) {  
25.         cur = zipEntry(p);  
26. //整个entry的字节数  
27. //存储rawlen需要的字节数  
28.   
29. /* Abort if there is no next entry. */  
30. if (p[rawlen] == ZIP_END) break;// 已经到达表尾,退出  
31. //得到下一个节点的zlentry  
32.   
33. /* Abort when "prevlen" has not changed. */  
34. // 如果下一的prevlen等于当前节点的rawlen,那么说明编码大小无需改变,退出  
35. if (next.prevrawlen == rawlen) break;  
36.   
37. // 下一节点的长度编码空间不足,进行扩展  
38. if (next.prevrawlensize < rawlensize) {  
39. /* The "prevlen" field of "next" needs more bytes to hold
40.              * the raw length of "cur". */  
41.             offset = p-zl;  
42. //需要扩展的字节数  
43.             zl = ziplistResize(zl,curlen+extra);  
44.             p = zl+offset;  
45.   
46. /* Current pointer and offset for next element. */  
47. //新的下一个节点的首地址  
48.             noffset = np-zl;  
49.   
50. /* Update tail offset when next element is not the tail element. */  
51. if ((zl+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) != np) {  
52.                 ZIPLIST_TAIL_OFFSET(zl) =  
53.                     intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra);  
54.             }  
55.   
56. /* Move the tail to the back. 这里的注释不是很清楚,需要自己思考*/  
57. //np+rawlensize新的下一个节点存储自身数据的首地址  
58. //np+next.prevrawlensize旧的下一个节点存储自身数据的首地址  
59. //将旧的下一个节点next的数据区到ziplist尾部全部向后偏移,空余出rawlensize个字节用来存储上个节点的长度  
60.             memmove(np+rawlensize,  
61.                 np+next.prevrawlensize,  
62.                 curlen-noffset-next.prevrawlensize-1);  
63. //空余出的rawlensize个字节存储上个节点的长度值  
64.   
65. /* Advance the cursor */  
66. //下一个节点  
67. //更新当前ziplist的长度  
68. else {  
69. // 下一节点的长度编码空间有多余,不进行收缩,只是将被编码的长度写入空间  
70. if (next.prevrawlensize > rawlensize) {  
71. /* This would result in shrinking, which we want to avoid.
72.                  * So, set "rawlen" in the available bytes. */  
73.                 zipPrevEncodeLengthForceLarge(p+rawlen,rawlen);  
74. else {  
75.                 zipPrevEncodeLength(p+rawlen,rawlen);  
76.             }  
77.   
78. /* Stop here, as the raw length of "next" has not changed. */  
79. break;//后面的节点不用扩展  
80.         }  
81.     }  
82. return zl;  
83. }




[cpp]  view plain copy print ?

1. /* Delete "num" entries, starting at "p". Returns pointer to the ziplist. */  
2. //从指针 p 开始,删除 num 个节点  
3. static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) {  
4. int i, totlen, deleted = 0;  
5. size_t offset;  
6. int nextdiff = 0;  
7.     zlentry first, tail;  
8.   
9. //删除的首个节点  
10. for (i = 0; p[0] != ZIP_END && i < num; i++) {  
11. //偏移到下个节点  
12.         deleted++;  
13.     }  
14.   
15. // 被删除的节点的总字节数  
16. if (totlen > 0) {  
17. if (p[0] != ZIP_END) {  
18. /* Storing `prevrawlen` in this entry may increase or decrease the
19.              * number of bytes required compare to the current `prevrawlen`.
20.              * There always is room to store this, because it was previously
21.              * stored by an entry that is now being deleted. */  
22. //计算删除的第一个节点first的prevrawlensize与p节点prevrawlensize的差值  
23.             nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);  
24. //根据nextdiff值,对p进行向前或向后偏移,留取的字节来保存first.prevrawlen  
25. //将first.prevrawlen值存储在p的prevrawlensize中  
26.   
27. /* Update offset for tail */  
28.             ZIPLIST_TAIL_OFFSET(zl) =  
29.                 intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))-totlen);  
30.   
31. /* When the tail contains more than one entry, we need to take
32.              * "nextdiff" in account as well. Otherwise, a change in the
33.              * size of prevlen doesn't have an effect on the *tail* offset. */  
34. //如果p不是尾节点,那么尾节点指针的首地址还需要加上nextdiff  
35.             tail = zipEntry(p);  
36. if (p[tail.headersize+tail.len] != ZIP_END) {  
37.                 ZIPLIST_TAIL_OFFSET(zl) =  
38.                    intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);  
39.             }  
40.   
41. /* Move tail to the front of the ziplist */  
42. //first.p至p之间的节点都是需要删除的,因此需要将p开始的数据向前偏移,zlend不需要处理,因此需要-1  
43.             memmove(first.p,p,intrev32ifbe(ZIPLIST_BYTES(zl))-(p-zl)-1);  
44. else {  
45. /* The entire tail was deleted. No need to move memory. */  
46. //如果已经删除到zlend,那么尾节点指针应该指向被删除的first之前的节点首地址  
47.             ZIPLIST_TAIL_OFFSET(zl) =  
48.                 intrev32ifbe((first.p-zl)-first.prevrawlen);  
49.         }  
50.   
51. /* Resize and update length */  
52.         offset = first.p-zl;  
53.         zl = ziplistResize(zl, intrev32ifbe(ZIPLIST_BYTES(zl))-totlen+nextdiff);  
54.         ZIPLIST_INCR_LENGTH(zl,-deleted);  
55.         p = zl+offset;  
56.   
57. /* When nextdiff != 0, the raw length of the next entry has changed, so
58.          * we need to cascade the update throughout the ziplist */  
59. /**
60.             如果nextdiff不等于0,说明现在的p节点的长度变了,需要级联更新下个节点能否保存
61.             p节点的长度值
62.         */  
63. if (nextdiff != 0)  
64.             zl = __ziplistCascadeUpdate(zl,p);  
65.     }  
66. return zl;  
67. }




[cpp]  view plain copy print ?

1. /* Insert item at "p". */  
2. //添加保存给定元素s的新节点插入到地址p之前,然后原有的数据向后偏移  
3. //zl: ziplist首地址,p:插入位置指针,s:待插入的字符串首地址,slen:待插入字符串长度  
4. static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {  
5. size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, prevlen = 0;  
6. size_t offset;  
7. int nextdiff = 0;  
8. char encoding = 0;  
9. long long value = 123456789; /* initialized to avoid warning. Using a value
10.                                     that is easy to see if for some reason
11.                                     we use it uninitialized. */  
12.     zlentry entry, tail;  
13.   
14. /* Find out prevlen for the entry that is inserted. */  
15. // 那么取出节点相关资料,以及 prevlen  
16. if (p[0] != ZIP_END) {//p之后存在节点  
17. //取出p节点的相关资料  
18. //得到p节点前一个节点所占字节数  
19. else {//p之后没有节点,达到zlend,那么就取出尾节点  
20. char *ptail = ZIPLIST_ENTRY_TAIL(zl);  
21. if (ptail[0] != ZIP_END) {//如果存在尾节点  
22. //得到尾节点的总字节数,然后在尾节点之后insert  
23. //否则就应该是在一个空的ziplist第一个insert一个节点  
24.     }  
25.   
26. /* See if the entry can be encoded */  
27. // 查看能否将新值保存为整数,如果可以的话返回 1 ,  
28. // 并将新值保存到 value ,编码形式保存到 encoding  
29. if (zipTryEncoding(s,slen,&value,&encoding)) {  
30. /* 'encoding' is set to the appropriate integer encoding */  
31. //s 可以保存为整数,那么继续计算保存它所需的空间  
32.         reqlen = zipIntSize(encoding);  
33. else {  
34. /* 'encoding' is untouched, however zipEncodeLength will use the
35.          * string length to figure out how to encode it. */  
36. // 不能保存为整数,直接使用字符串长度  
37.         reqlen = slen;  
38.     }  
39. /* We need space for both the length of the previous entry and
40.      * the length of the payload. */  
41. // 计算编码 prevlen 所需的长度  
42.     reqlen += zipPrevEncodeLength(NULL,prevlen);  
43. //计算编码slen所需的长度  
44.     reqlen += zipEncodeLength(NULL,encoding,slen);  
45.   
46. /* When the insert position is not equal to the tail, we need to
47.      * make sure that the next entry can hold this entry's length in
48.      * its prevlen field. */  
49. //当插入的位置不为尾部时,需要确保下一个节点的存储前一个  
50. //节点所占自己数的空间能够存储即将插入节点的长度  
51. // zipPrevLenByteDiff 的返回值有三种可能:  
52. // 1)新旧两个节点的编码长度相等,返回 0  
53. // 2)新节点编码长度 > 旧节点编码长度,返回 5 - 1 = 4  
54. // 3)旧节点编码长度 > 新编码节点长度,返回 1 - 5 = -4  
55.     nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;  
56.   
57. /* Store offset because a realloc may change the address of zl. */  
58. //保存当前的偏移量,在这偏移量之前的数据不需要改变,只需要改变在此之后的数据  
59. // 重分配空间,并更新长度属性和表尾  
60. // 新空间长度 = 现有长度 + 新节点所需长度 + 编码新节点长度所需的长度差  
61.     zl = ziplistResize(zl,curlen+reqlen+nextdiff);  
62.     p = zl+offset;  
63.   
64. /* Apply memory move when necessary and update tail offset. */  
65. if (p[0] != ZIP_END) {  
66. /* Subtract one because of the ZIP_END bytes */  
67. //将原有从p-nextdiff开始全部向后偏移,余留出reqlen保存即将insert的数据  
68. /**
69.             nextdiff = -4:原来p有5个字节来存储上个节点的长度,而现在只需要1个,
70.                           因此只需要将p+4后面的字节偏移到p+reqlen即可,这样就
71.                           只保留1个字节保存reqlen的长度了
72.             nextdiff = 4: 原来p只有1个字节来存储上个节点的长度,现在需要5个,
73.                           那就将p-4后面的字节偏移到p+reqlen,这样p原来有1个字节
74.                           加上多偏移来的4个字节就可以保存reqlen的长度了
75.             nextdiff = 0: 不需要考虑
76.         */  
77.         memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);  
78.   
79. /* Encode this entry's raw length in the next entry. */  
80. //下个节点保存即将insert数据的所占字节数  
81.   
82. /* Update offset for tail */  
83.         ZIPLIST_TAIL_OFFSET(zl) =  
84.             intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);  
85.   
86. /* When the tail contains more than one entry, we need to take
87.          * "nextdiff" in account as well. Otherwise, a change in the
88.          * size of prevlen doesn't have an effect on the *tail* offset. */  
89. // 有需要的话,将 nextdiff 也加上到 zltail 上  
90.         tail = zipEntry(p+reqlen);  
91. if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {  
92.             ZIPLIST_TAIL_OFFSET(zl) =  
93.                 intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);  
94.         }  
95. else {  
96. /* This element will be the new tail. */  
97. // 更新 ziplist 的 zltail 属性,现在新添加节点为表尾节点  
98.         ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);  
99.     }  
100.   
101. /* When nextdiff != 0, the raw length of the next entry has changed, so
102.      * we need to cascade the update throughout the ziplist */  
103. /**
104.         如果nextdiff不等于0,说明现在的p+reqlen节点的长度变了,需要级联更新下个节点能否保存
105.         p+reqlen节点的长度值
106.     */  
107. if (nextdiff != 0) {  
108.         offset = p-zl;  
109.         zl = __ziplistCascadeUpdate(zl,p+reqlen);  
110.         p = zl+offset;  
111.     }  
112.   
113. /* Write the entry */  
114. //填写保存上一个节点长度的字节数  
115. //填写保存当前节点长度的字节数  
116. if (ZIP_IS_STR(encoding)) {//保存当前节点的字符串  
117.         memcpy(p,s,slen);  
118. else {  
119. //整数  
120.     }  
121. //length + 1  
122. return zl;  
123. }

ziplist剩余的一些函数就不一一列举分析,理解上述三个函数,其余都很简单,关注这三个函数的重点是作者在realloc之后如何通过memmove保证后续节点的数据保持不变的,不得不说Redis的作者对ziplist的实现很让人佩服。

九、小结

ziplist的最大的好处就是相比skiplist节约大量内存,但是在插入、删除、查询等操作上的时间复杂度与skiplist都是无法比拟的,当保存的数据比较少时,操作的时间自然可以接受,内存就是关键因素。

ziplist在Redis中主要用于Hash Table、List、Sorted Set数据类型小范围数据的存储,本文描述的ziplist的存储都是无序的,如何实现在Sorted Set中的有序存储待以后分析,无序变有序无疑又增加的时间复杂度。

总之,ziplist就是一种用时间换空间的策略。