标签:
身体被掏空了一星期, 前天终于挣扎着继续做这个代码走读
不得不说, 压缩列表的实现复杂程度还是超出了我的预计
破天荒的第一次, 我必须手动上注释, 才能防止自己迷失在代码里面。
今天还没有录制视频, 最近一直在做公司的事, 时间也比较紧, 所以今天只是把我经过注释的压缩列表部分的代码贴出来, 同时给出一个阅读建议
在学习压缩列表的过程中, 我也参考了黄健宏先生对Redis2.6源代码的注释, 发现了黄先生注释中的一个错误。
如下:
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip) { int skipcnt = 0; unsigned char vencoding = 0; long long vll = 0; // 遍历整个列表 while (p[0] != ZIP_END) { unsigned int prevlensize, encoding, lensize, len; unsigned char *q; // 编码前一个节点的长度所需的空间 ZIP_DECODE_PREVLENSIZE(p, prevlensize); // 当前节点的长度 ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len); // 保存下一个节点的地址 q = p + prevlensize + lensize; .... ....(后略) } // 该注释中, 关于q = p + prevlensize + lensize的注释是错误的
好了, 碎话就不多说了, 首先, 说一下建议大家阅读ziplist模块的顺序
1: 了解ziplist的设计(可以看上一篇博文视频)
2: 看懂ziplist.h中的所有宏函数, 之后再开始看ziplist.c中各个API的实现
3: ziplistNew()很好看懂, 之后直接看ziplistPush(..)函数。 该函数的具体实现牵扯到了很多ziplist.c中的静态函数, 配合我的具体注释, 慢慢把流程理清楚。 重点在于阅读理解__ziplistCascadeUpdate(...)是如何处理可能发生的链锁反应的。
4: 读完ziplistPush(...)函数, 整个ziplist模块就看完了一大半, 剩余的公开接口函数里, 都是比较简单的。 在阅读相关删除结点操作的函数时, 注意删除操作同样可能会引起链锁反应。 注意删除操作引起“前驱结点字节长度”字段的值可能会下降, 但若其之前占用的是5字节, 那么无论值收缩为多少, 空间都不会再收缩。
5: 注意, 注释中的两个词语“元素”“结点”是混着用的, 两者表示的是同一意思。 阅读的时候注意, 希望这一点瑕疵不会影响到你对ziplist的理解。
6: 特别注意结构体zlentry中lensize字段与len字段的真实含义。 黄先生的注释错误很大可能上就是曲解了这两个字段的实际含义
下面贴代码
// ziplist.h #ifndef ZIPLIST_H #define ZIPLIST_H #include <stddef.h> #include <stdint.h> #include <string.h> #include <assert.h> #define ZIPLIST_HEAD 0 #define ZIPLIST_TAIL 1 #define ZIP_END 255 #define ZIP_BIGLEN 254 // specification of entry of ziplist /* encoding[0] sizeof(encoding) sizeof(content) content type */ #define ZIP_STR_MASK 0xc0 /* 1100 0000 ------THE MASK OF STR ENCODING---- ------------ */ #define ZIP_INT_MASK 0x30 /* 0011 0000 ------THE MASK OF INT EOCODING---- ------------ */ #define ZIP_STR_06B (0 << 6) /* 00bb bbbb 1 byte bb bbbb binary */ #define ZIP_STR_14B (1 << 6) /* 01bb bbbb bb.. 2 bytes bb bbbb bbbb bbbb binary */ #define ZIP_STR_32B (2 << 6) /* 10-- ---- bb.. 5 bytes 0xBB BB BB BB binary */ #define ZIP_INT_16B (0xc0 | 0 << 4) /* 1100 0000 1 byte 2 bytes integer */ #define ZIP_INT_32B (0xc0 | 1 << 4) /* 1101 0000 1 byte 4 bytes integer */ #define ZIP_INT_64B (0xc0 | 2 << 4) /* 1110 0000 1 byte 8 bytes integer */ #define ZIP_INT_24B (0xc0 | 3 << 4) /* 1111 0000 1 byte 3 bytes integer */ #define ZIP_INT_8B 0xfe /* 1111 1110 1 byte 1 byte integer */ /* 1111 vvvv 1 byte 0 byte integer */ #define ZIP_INT_IMM_MASK 0x0f /* 0000 1111 THE MASK OF 4 BITS VALUE---------------------------*/ #define ZIP_INT_IMM_MIN 0Xf1 /* 1111 0001 THE MIN VALUE OF 4 BITS VALUE TYPE-----------------*/ #define ZIP_INT_IMM_MAX 0xfd /* 1111 1101 THE MAX VALUE OF 4 BITS VALUE TYPE-----------------*/ #define ZIP_INT_IMM_VAL(v) (v & ZIP_INT_IMM_MASK) #define INT24_MAX 0x7fffff #define INT24_MIN (-INT24_MAX - 1) /* 判断编码是否为字符串编码, 即encoding[0]高两位不为11的编码 */ #define ZIP_IS_STR(enc) (((enc) & ZIP_STR_MASK) < ZIP_STR_MASK) #define ZIPLIST_BYTES(zl) (*((uint32_t *)(zl))) /* 取“总字节长度” */ #define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t *)((zl)+sizeof(uint32_t)))) /* 取“尾结点偏移量” */ #define ZIPLIST_LENGTH(zl) (*((uint16_t *)((zl)+sizeof(uint32_t)*2))) /* 取“总结点数量” */ #define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t)) /* 固定返回10, 即头信息所占字节数 */ #define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE) /* 取头结点 */ #define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) /* 取尾结点 */ #define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1) /* 取结束标记 */ /* 增加“总结点数量”字段的值 注意 : 1: 该字段值最大为65535, 当值小于65535时, 值代表的是真实的结点数量。 当值为65535时, 真实数量需要遍历统计 2: 递增量应该始终为1, 因为当该宏被调用时, 应始终在插入结点的相关函数中调用, 而一次只能插入一个结点 */ #define ZIPLIST_INCR_LENGTH(zl, incr) { if (ZIPLIST_LENGTH(zl) < UINT16_MAX) ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl)) + incr); } /* 取得结点的 “前驱结点长度字段所占用的字节数”, 保存在prevlensize中 */ #define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do { if((ptr)[0] < ZIP_BIGLEN) { (prevlensize) = 1; } else { (prevlensize) = 5; } } while(0); /* 取得结点的 “前驱结点长度字段所占用的字节数”, 以及“前驱结点长度字段的值” 分别存储在prevlensize与prevlen中 */ #define ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen) do { \ ZIP_DECODE_PREVLENSIZE(ptr, prevlensize); if((prevlensize) == 1) { (prevlen) = (ptr)[0]; } else if ((prevlensize) == 5) { assert(sizeof((prevlensize)) == 4); memcpy(&(prevlen), ((char *)(ptr)) + 1, 4); memrev32ifbe(&prevlen); } } while(0); /* 取得结点的 “编码方式”, 存储在encoding中 */ #define ZIP_ENTRY_ENCODING(ptr, encoding) do { \ (encoding) = (ptr[0]); if((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; } while(0); // 0x3f == 0011 1111 b /* 取得结点的“编码方式”, “编码方式字段所占用的字节数”, “数据区字节长度” 分别存储在encoding, lensize, len中 */ #define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do { \ ZIP_ENTRY_ENCODING((ptr), (encoding)); 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); 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; unsigned char * ziplistNew (void); unsigned char * ziplistPush (unsigned char *zl, unsigned char *s, unsigned int slen, int where); unsigned char * ziplistIndex (unsigned char *zl, int index); unsigned char * ziplistNext (unsigned char *zl, unsigned char *p); unsigned char * ziplistPrev (unsigned char *zl, unsigned char *p); unsigned int ziplistGet (unsigned char *p, unsigned char **sval, unsigned int *slen, long long *lval); unsigned char * ziplistInsert (unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen); unsigned char * ziplistDelete (unsigned char *zl, unsigned char **p); unsigned char * ziplistDeleteRange (unsigned char *zl, unsigned int index, unsigned int num); unsigned int ziplistCompare (unsigned char *p, unsigned char *sstr, unsigned int slen); unsigned char * ziplistFind (unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip); unsigned int ziplistLen (unsigned char *zl); size_t ziplistBlobLen (unsigned char *zl); #endif // ZIPLIST_H
// ziplist.c #include "endianconv.h" #include "util.h" #include "ziplist.h" #include "zmalloc.h" static unsigned char * __ziplistInsert (unsigned char * zl, unsigned char * p, unsigned char * s, unsigned int slen); static zlentry zipEntry (unsigned char * p); static unsigned int zipRawEntryLength (unsigned char * p); static int zipTryEncoding (unsigned char * entry, unsigned int entrylen, long long * v, unsigned char * encoding); static unsigned int zipIntSize (unsigned char encoding); static unsigned int zipPrevEncodeLength (unsigned char * p, unsigned int len); static unsigned int zipEncodeLength (unsigned char * p, unsigned char encoding, unsigned int rawlen); static int zipPrevLenByteDiff (unsigned char * p, unsigned int len); static unsigned char * ziplistResize (unsigned char * zl, unsigned int len); static void zipSaveInteger (unsigned char * p, int64_t value, unsigned char encoding); static unsigned char * __ziplistCascadeUpdate (unsigned char * zl, unsigned char * p); static void zipPrevEncodeLengthForceLarge(unsigned char * p, unsigned int len); static int64_t zipLoadInteger (unsigned char * p, unsigned char encoding); static unsigned char * __ziplistDelete (unsigned char * zl, unsigned char * p, unsigned int num); /* 删除指定位置后的连续num个元素 参数: zl ziplist地址 p 被删除的连续多个元素中的第一个元素的地址 num 删除的元素的个数 */ static unsigned char * __ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num){ unsigned int i; unsigned int totlen; unsigned int deleted = 0; size_t offset; int nextdiff = 0; zlentry first; zlentry tail; first = zipEntry(p); // first存储第一个即将被删除的元素的信息 for(i = 0; p[0] != ZIP_END && i < num; ++i) { // 通过循环, 让p指向被删除的最后一个元素的后继, 且用deleted记录实际将要删除的元素个数 p += zipRawEntryLength(p); // 因为存在p之后并没有num个元素这种情况, 所以deleted和num不一定相等 deleted++; } totlen = p - first.p; // totlen存储的是总共要删除的连续字节的数目 if(totlen > 0) { if(p[0] != ZIP_END) { // 判断最后一个被删除的元素的后继是不是结束标记, 如果不是 nextdiff = zipPrevLenByteDiff(p, first.prevrawlen); // 则删除操作可能会引起链锁更新, 所以先用nextdiff存储 “最后一个被删除的元素的后继元素的‘前驱元素长度‘字段”需要扩展的字节数, 该值为0或4 p -= nextdiff; // 然后p向前移nextdiff个字节, 这时, p代表的就是“最后一个要被删除的字节之后的那个字节” zipPrevEncodeLength(p, first.prevrawlen); // 这时, 相当于给“最后一个被删除的元素的后继元素的‘前驱元素长度‘字段”进行了扩充, 同时更新这个字段中的值 ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)) - totlen); // 更新“表尾结点偏移量”字段, 注意,当nextdiff不为0时, 此值并不正确 tail = zipEntry(p); // 用tail保存“最后一个被删除的元素的后继元素的信息” if(p[tail.headersize + tail.len] != ZIP_END) { // 如果当前p所指向的元素的后继不是结束标记, 那么就需要修正一下ziplist的“表尾结点偏移量”, 此处原理同插入元素时的情形 ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)) + nextdiff); } memmove(first.p, p, intrev32ifbe(ZIPLIST_BYTES(zl))-(p-zl)-1); // 将p之后的字节移动到删除位置上 } else { // 如果是 ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe((first.p-zl) - first.prevrawlen); // 只需要更新ziplist的“表尾结点偏移量”即可 } offset = first.p - zl; // 用offset记录第一个被删除的元素的偏移量 zl = ziplistResize(zl, intrev32ifbe(ZIPLIST_BYTES(zl)) - totlen + nextdiff); // 重新为ziplist分配空间 ZIPLIST_INCR_LENGTH(zl, -deleted); // 更新ziplist的结点计数 p = zl+offset; // 还原p指针 if(nextdiff != 0) { // 如果nextdiff不为0, 则调用__ziplistCascadeUpdate处理链锁更新 zl = __ziplistCascadeUpdate(zl, p); } } return zl; } /* 将一个值, 写入一个元素的“前驱元素长度”字段中去, 注意: 1: 要求该元素的“前驱元素长度”字段为5字节字段 2: 值可以是254以下的值, 即便是254以下的值, 也会被强行写入后四字节 参数: p 元素地址 len 新的“前驱元素长度”字段的值 */ static void zipPrevEncodeLengthForceLarge(unsigned char *p, unsigned int len) { if(p == NULL) return; p[0] = ZIP_BIGLEN; // 第一个字节写固定值254 memcpy(p+1, &len, sizeof(len)); // 将四字节值写入 memrev32ifbe(p+1); // 调整为小端字节序 } /* 处理链锁扩展的情况 参数: zl ziplist的地址 p 可能会导致其后继元素链锁扩展的元素的地址 */ static unsigned char * __ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) { size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)); // 当前ziplist的总长度 size_t rawlen; size_t rawlensize; size_t offset; size_t noffset; size_t extra; unsigned char * np; // np始终指向p的下一个元素 zlentry cur, next; while(p[0] != ZIP_END) { // 循环, 直到p达到结束标记时停止 cur = zipEntry(p); // 用cur变量把p元素的信息存储起来 rawlen = cur.headersize + cur.len; // 用rawlen变量保存p元素的字节长度 rawlensize = zipPrevEncodeLength(NULL, rawlen); // 用rawlensize变量保存 “存储p元素的字节长度需要占用的字节数” if(p[rawlen] == ZIP_END) // 如果p元素的后继为结束标记, 则退出循环, 表明已经循环到了最后一个元素 break; next = zipEntry(p + rawlen); // 用next变量把p后继的信息保存起来 if(next.prevrawlen == rawlen) // 如果next中存储的 “前驱元素字节长度” 与当前p元素的字节长度一致, 则表示p不会引起后继扩展, 链锁扩展停止 break; if(next.prevrawlensize < rawlensize) { // 如果next中的 “前驱元素字节长度”字段占用的字节数 小于 “保存当前p元素的字节长度所需要的字节数”, 则说明p将引起后继的扩展 offset = p - zl; // 先保存p的偏移量 extra = rawlensize - next.prevrawlensize; // 保存后继扩展需要的额外空间, 一般情况下都是从1扩展到5, 即为5-1 == 4 zl = ziplistResize(zl, curlen+extra); // 扩展ziplist p = zl +offset; // 重新定位p np = p + rawlen; // np指向p的后继 noffset = np-zl; // 保存np的偏移量 if((zl+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) != np) { // 在np不是最后一个元素的情况下, np的扩展将引起ziplist的“尾结点偏移量”这个字段值的变化 ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)) + extra); } memmove(np + rawlensize, np+next.prevrawlensize, curlen-noffset-next.prevrawlensize-1); // 顺移ziplist中的数据 zipPrevEncodeLength(np, rawlen); // 更新np的“前驱元素长度”字段的值 p += rawlen; // p指向下一个元素 curlen += extra; // ziplist的总长度递增 } else { // 如果next中的 “前驱元素字节长度”字段占用的字节数 [不小于] “保存当前p元素的字节长度所需要的字节数”, 则说明p不会引起后继的扩展 if(next.prevrawlensize > rawlensize) { // 如果next中的 “前驱元素字节长度”字段占用的字节数 [大于] “保存当前p元素的字节长度所需要的字节数”, 则说明p不仅不会引起后继的扩展, 其实还需要收缩 zipPrevEncodeLengthForceLarge(p+rawlen, rawlen);// 但Redis的处理是并不收缩, 而是强行把1字节的值写到5字节的字段中的后4字节中去。 这里调用到了zipPrevEncodeLengthForceLarge函数 } else { // 如果next中的 “前驱元素字节长度”字段占用的字节数 [等于] “保存当前p元素的字节长度所需要的字节数”, 则说明p并不会引起后继的扩展, 一切都刚刚好 zipPrevEncodeLength(p + rawlen, rawlen); // 这时只需要更新后继结点的 “前驱元素字节长度” 这个字段的值就可以了 } break; // 无论是大于, 还是等于, 都表明链锁反应已经停止 } } return zl; } /* 将整数值存储在encoding为数字编码的元素内容字段中 参数: p 应指向元素的内容字段, 而不是元素的首地址 value 要存储的值 encoding 当前元素的编码方式 */ static void zipSaveInteger (unsigned char * p, int64_t value, unsigned char encoding) { int16_t i16; int32_t i32; int64_t i64; if(encoding == ZIP_INT_8B) { // 判断编码是否为8B ((int8_t *)p)[0] = (int8_t)value; // 如果是, 直接写入一字节 } else if(encoding == ZIP_INT_16B) { // 判断编码是否为16B i16 = value; // 如果是, 则用i16把值保存起来 memcpy(p, &i16, sizeof(i16)); // 写入 memrev16ifbe(p); // 转换为小端字节序 } else if(encoding == ZIP_INT_24B) { // 判断编码是否为24B i32 = value << 8; // 如果是, 用i32变量存储value左移8位后的值, 此时, i32变量的四个字节中, 最高地址字节不存储在效数据 memrev32ifbe(&i32); // 把i32变量转换为小端字节序, 此时, i32变量的四个字节中, 最低地址字节不存储有效数据 memcpy(p, ((uint8_t *)&i32)+1, sizeof(i32)-sizeof(uint8_t)); // 把i32变量四个字节中的第2, 3, 4字节中的数据存储在p指向的内容区中 } else if(encoding == ZIP_INT_32B) { // 判断编码是否为32B i32 = value; // 如果是, 用i32变量将值存储起来 memcpy(p, &i32, sizeof(i32)); // 写入 memrev32ifbe(p); // 转换为小端字节序 } else if(encoding == ZIP_INT_64B) { // 判断编码是否为64B i64 = value; // 如果是, 用i64变量将值存储起来 memcpy(p, &i64, sizeof(i64)); // 写入 memrev64ifbe(p); // 转换为小端字节序 } else if(encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX) { // 判断编码是否为4bit形式的编码 // 如果是, 则什么也不做, 因为“编码”字段已经存储了一个值 } else { assert(NULL); // 不应该到达的一个分支, 如果到达, 说明传入的encoding参数不正确, 是一个非整数编码的参数 } } /* 对ziplist进行扩容, 返回扩容后的ziplist的头地址 参数: zl ziplist的头地址 len ziplist所需要的新长度 */ static unsigned char * ziplistResize(unsigned char * zl, unsigned int len) { zl = zrealloc(zl, len); // 重新分配空间 ZIPLIST_BYTES(zl) = intrev32ifbe(len); // 重置ziplist中的“总长度”字段 zl[len - 1] = ZIP_END; // 设置结束标记字节 return zl; } /* 判断一个元素, 当其前驱元素的长度变更为len时, 其自身的“前驱元素所占用长度”字段所需要的新长度与原字段长度之差 举例, p当前指向的元素, 其“前驱元素所占用长度”字段长度为1 len值为256, 则如果“前驱元素所占用长度”字段的值要表示为256, “前驱元素所占用长度”这个字段就要扩容至5字节 即要返回5 - 1 == 4 参数: p 指向某个元素 len “前驱元素所占用长度”的新值 */ static int zipPrevLenByteDiff(unsigned char * p, unsigned int len) { unsigned int prevlensize; //使用宏, 将当前元素的“前驱元素所占用长度”字段所占用的长度存储在变量prevlensize中 ZIP_DECODE_PREVLENSIZE(p, prevlensize); //使用zipPrevEncodeLength函数, 计算出当前驱元素的长度为len时, 当前元素的“前驱元素所占用长度”字段所需要的长度, 并减去当前元素的“前驱元素所占用长度”的长度 return zipPrevEncodeLength(NULL, len) - prevlensize; } /* 计算存储一个元素的编码所需要的字节数, 即计算某个元素的encoding字段本身所占用的长度 参数: p 应该指向当前元素的“编码”字段, 或为NULL encoding 编码方式 rawlen 元素内容所占用的字节数 */ static unsigned int zipEncodeLength(unsigned char * p, unsigned char encoding, unsigned int rawlen) { unsigned char len = 1; unsigned buf[5]; if(ZIP_IS_STR(encoding)) { // 判断编码方式是否为字符串编码, 如果是 if(rawlen <= 0x3f) { // 判断元素的内容是否超过63字节, 如果没有(63字节以内) if(!p) // 判断p是否为空, 如果为空 return len; // 直接返回1, 代表编码字段1字节足够 buf[0] = ZIP_STR_06B | rawlen; // 此时使用的编码为 00xxxxxx, 将长度字段存储在后六位xxxxxx中 } else if(rawlen <= 0X3fff) { // 判断元素的内容是否超过16383字节, 如果没有(16383字节以内) len += 1; // 则此时必须要用两个字节来存储编码字段 if(!p) // 判断p是否为空, 如果为空 return len; // 直接返回2, 代表编码字段2字节足够 buf[0] = ZIP_STR_14B | ((rawlen >> 8) & 0x3f); // 此时使用的编码形式为, 01xxxxxx yyyyyyyy, 将长度值右移八位取到高位的xxxxxx buf[1] = rawlen & 0xff; // 把取长度值的低八位yyyyyyyy, 写进buf中 } else { // 此时说明元素的内容超过了163838字节 len += 4; // 此时需要用五个字节来存储编码字段 if(!p) // 判断p是否为空, 如果为空 return len; // 直接返回5 buf[0] = ZIP_STR_32B; // 否则编码形式是这样的: 10______ aaaaaaaa bbbbbbbb cccccccc dddddddd buf[1] = (rawlen >> 24) & 0xff; // 内容的长度会被存储在后四字节中, 第一字节剩下的六位没有用处 buf[2] = (rawlen >> 16) & 0xff; buf[3] = (rawlen >> 8) & 0xff; buf[4] = rawlen & 0xff; } } else { // 如果编码方式不是字符串编码 if(!p) // 判断p是否为空 return len; // 如果为空, 直接返回1, 因为整数编码始终只占用1字节 buf[0] = encoding; // 把编码值直接写进buf中 } memcpy(p, buf, len); // 最终把编码值通过memcpy复制到p所指向的地址中去, 故参数p应该指向当前元素的“编码字段”, 或者为空 return len; } /* 计算存储一个元素的长度所需要的字节数, 即计算某个元素的“前驱元素长度”字段所需要占用的字节数 参数: p 应该指向当前元素(也是当前元素的“前驱元素长度”字段的地址), 可为NULL len 应该是前驱元素所占用的字节数 */ static unsigned int zipPrevEncodeLength(unsigned char * p, unsigned int len) { if(p == NULL) { // 判断前驱元素所占用的字节数, 是否可以用1字节存储, 即该值是否小于254, 若小于, 返回1, 代表“前驱元素长度”用1字节即可存储 // 否则, 返回5 return (len < ZIP_BIGLEN) ? 1 : sizeof(len) + 1; } else { // 判断前驱元素所占用的字节数, 是否可用1字节存储 // 如果可以, 直接将这个1字节值存储在p[0]处, 此时, “前驱元素长度”该字段仅占用1字节, 其值即存储在p[0]这个内存字节单元处 // 并返回1 if(len < ZIP_BIGLEN) { p[0] = len; return 1; } else { // 否则, 置p[0]为254 // 将len值转换为小端字节序, 存储在p[1~4]这四个字节中, 并返回5 p[0] = ZIP_BIGLEN; memcpy(p+1, &len, sizeof(len)); memrev32ifbe(p+1); return 1 + sizeof(len); } } } /* 如果编码方式是整数编码, 则给定编码方式, 返回在该编码方式下一个元素的内容所占用的字节数 该函数被宏 ZIP_DECODE_LENGTH调用, 也被函数__ziplistInsert调用 */ static 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位半字节内容, 内容被编码在“编码字段”里, 所以不占用额外的空间, 故返回0 } assert(NULL); // should never go here return 0; } /* 检查entry所指向的值, 尝试将其编码为整数编码 如果可以, 则将值保存在*v中, 把编码保存在*encoding中, 并返回1 否则返回0 */ static int zipTryEncoding(unsigned char * entry, unsigned int entrylen, long long * v, unsigned char * encoding) { long long value; // 如果entry作为一个字符串形式的整数, 其字符数量大于等于32个(十进制数字大于32位), 或其字符数量为0, 直接判负 if(entrylen >= 32 || entrylen == 0) return 0; // 否则调用string2ll尝试将entry解读为一个字符形式表示的十进制数字, 并将其值翻译为数值类型并存储在临时变量value中 if(string2ll((char *)entry, entrylen, &value)) { // 根据value的值选择最合适的编码 if(value >= 0 && value <= 12) { *encoding = ZIP_INT_IMM_MIN + value; } else if(value >= INT8_MIN && value <= INT8_MAX) { *encoding = ZIP_INT_8B; } else if(value >= INT16_MIN && value <= INT16_MAX) { *encoding = ZIP_INT_16B; } else if(value >= INT24_MIN && value <= INT24_MAX) { *encoding = ZIP_INT_24B; } else if(value >= INT32_MIN && value <= INT32_MAX) { *encoding = ZIP_INT_32B; } else { *encoding = ZIP_INT_64B; } // 最终把value的值存储在*v中 *v = value; // 返回1, 表示尝试编码成功 return 1; } return 0; } /* 计算一个ziplist中的元素所占字节数 */ static unsigned int zipRawEntryLength(unsigned char * p) { unsigned int prevlensize; unsigned int encoding; unsigned int lensize; unsigned int len; ZIP_DECODE_PREVLENSIZE(p, prevlensize); // 计算“前驱元素长度字段”所占用的字节数 ZIP_DECODE_LENGTH(p+prevlensize, encoding, lensize, len); // 计算“编码字段”与“内容字段”两字段所占用的字节数 return prevlensize + lensize + len; // 返回 三个字段 总的占用的字节数, 即为一个元素所占用的字节数 } /* 将ziplist中的一个元素的内容保存到zlentry变量中, 并返回之 参数: p p应指向ziplist中的一个元素的起始地址 备注: 一个元素在ziplist中有三个字段, 分别如下: 前驱元素长度 代表前驱元素所占用的字节数, 1或5字节 编码方式 代表当前元素的编码方式, 1或2或5字节 内容 当前元素的值, 数据。 最小可为0字节, 即不占用空间, 不存在该字段 */ static zlentry zipEntry(unsigned char * p) { zlentry e; // 调用ZIP_DECODE_PREVLEN宏取得当前元素的"前驱元素长度"字段本身的长度, 与该字段的值 // 并将这两个值分别存储在e.prevrawlensize与e.prevrawlen字段中 ZIP_DECODE_PREVLEN(p, e.prevrawlensize, e.prevrawlen); // 调用ZIP_DECODE_LENGTH宏, 取得当前元素的“编码方式”“编码方式字段所占字节数”“内容所占字节数” // 分别存储在e.encoding, e.lensize, e.len三个字段中 ZIP_DECODE_LENGTH(p+e.prevrawlensize, e.encoding, e.lensize, e.len); // 将“前驱元素长度字段所占字节数”与"当前元素编码字段所占字节数", 统称为“元素头长度”, 存储在e.headersize字段中 e.headersize = e.prevrawlensize + e.lensize; // 将当前元素的地址存储在e.p字段中 e.p = p; return e; } /* 非公开函数, 向ziplist中指定位置插入一个元素 参数: zl 被插入的ziplist的地址 p 插入的位置 s 即将被插入的元素的内容的地址 slen 即将被插入的元素的内容占用的字节数 */ static unsigned char * __ziplistInsert(unsigned char * zl, unsigned char * p, unsigned char * s, unsigned int slen) { size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)); // 当前ziplist所占用的总字节数 size_t reqlen; // 新元素所要占用的字节数 size_t prevlen = 0; // 新元素前驱元素所占用的字节数 size_t offset; // int nextdiff = 0; // unsigned char encoding = 0; // long long value = 123456789; // 初始化该值以防编译器报警 zlentry entry; zlentry tail; if(p[0] != ZIP_END) { // 判断插入位置p不是ziplist的结束标记, 如果不是(至少说明ziplist不为空) entry = zipEntry(p); // 则先用entry变量把p所指向的元素存储起来 prevlen = entry.prevrawlen; // 并用prevlen变量存储p所指向的元素的前驱元素所占用的字节数 } else { // 否则(ziplist可能为空, 或ziplist不为空但插入位置是尾部) unsigned char * ptail = ZIPLIST_ENTRY_TAIL(zl); // 用ptail指针保存ziplist的最后一个元素的地址 if(ptail[0] != ZIP_END) { // 判断ptail是否成功指向结束标记, 如果不是(即ptail确实指向了最后一个元素, 即ziplist不是空表) prevlen = zipRawEntryLength(ptail); // 用prevlen保存ptail所指向元素的前一个元素所占的字节数(即ziplist不为空, 插入位置为尾部) } else { // 否则 // 什么也不做(这时, 已经确定了ziplist不为空, 所以ptail绝对不会指向结束标记, 该分支不可能执行到) } } // 此时 // p指向插入位置 // prevlen == 插入位置之前的元素所占用的字节数(如果表为空, 则该变量值为0) if(zipTryEncoding(s, slen, &value, &encoding)) { // 判断新元素的内容是否可以被视为整数,如果可以 reqlen = zipIntSize(encoding); // 用reqlen变量存储“新元素的内容需要占用的字节数” } else { // 否则 reqlen = slen; // 用reqlen变量存储在函数入参处声称的“新元素的内容需要占用的字节数” } reqlen += zipPrevEncodeLength(NULL, prevlen); // 给reqlen累加上存储“前驱元素长度字段的长度”, 其值为1或5 reqlen += zipEncodeLength(NULL, encoding, slen); // 给reqlen累加上存储“编码字段的长度”, 其值为1或2或5 // 此时 // p指向插入位置 // prevlen == 插入位置之前的元素所占用的字节数(如果表为空, 则为0) // reqlen == 待插入的元素将要占用的字节数 // 在插入了一个元素后, 可能会导致链锁反应 // 如原来的元素为 A B D // 当插入之后变成 A B C D 时, D的“前驱元素所占用的字节数”这个字段就会发生改变 // 这可能会导致元素D要扩容, 即D的“前驱元素所占用的字节数”从1字节变成5字节 // 可能会链锁式的导致D之后的E也要扩容, E扩容也会导致F扩容。。等等, 这是一种极端特殊的情况 // nextdiff就是计算, 插入前与插入后, D所占用的空间的差异, 换句话说, 就是通过nextdiff来判断D是否要扩容 // 当插入位置为结束标记时, 不会发生扩容事件, 因为插入位置之后没有元素 // 只有当插入位置不为结束标记时, 才需要通过zipPrevLenByteDiff来判断后继元素是否要扩容 nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p, reqlen) : 0; // offset将存储插入位置与ziplist首地址之间的偏移量, 因为虽然p指向的是插入位置的地址, 但ziplist本身可能会被重分配, 所以用p来标记插入位置是不可靠的 offset = p - zl; // 重新为ziplist分配空间, 新分配的空间 == 当前ziplist的总长度 + 新元素所需要的总长度 + 插入之后可能会导致的后继元素扩容长度 zl = ziplistResize(zl, curlen + reqlen + nextdiff); // 计算出ziplist重新分配空间后, 新的插入位置 p = zl + offset; // 至此, ziplist总体已经经过了扩容, 且ziplist的“总长度”字段已经过更新, 新的结束标记被设置在ziplist结尾, 但旧的ziplist标记依然存在 // p指向插入位置 // prevlen == 插入位置之前的元素所占用的字节数(如果表为空, 则为0) // reqlen == 待插入的元素将要占用的字节数 if(p[0] != ZIP_END) { // 判断插入位置是否为旧的结束标记, 如果不是 // (curlen - offset - 1 + nextdiff == 原表长度 - 到插入位置的偏移量 - 1 + nextdiff == 插入位置之后的长度 + nextdiff) memmove(p + reqlen, p-nextdiff, curlen-offset-1+nextdiff); // 则将插入位置之后的元素顺移向后挪, memmove(dest, src, n), 注意src是p-nextdiff, 这是为后继结点预留了扩容的长度 zipPrevEncodeLength(p+reqlen, reqlen); // 将插入元素的长度, 编码到后继元素的“前驱结点长度”字段中。 此时, 插入位置之后的所有元素迁移完毕 ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)) + reqlen); // 更新ziplist本身的“表尾结点偏移量”字段的值 tail = zipEntry(p + reqlen); // 用tail变量存储插入位置后继元素的信息 if(p[reqlen+tail.headersize + tail.len] != ZIP_END) { // 判断插入位置后继的后继是否为结束标记, 如果不是 ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)) + nextdiff);// 则将ziplist的表尾偏移量再累加一个nextdiff /* 这里比较有意思, 插入元素只看后继的话, 有三种情况, 如下 , 注意在下三种情况下, [前驱]本身可能不存在 第一种情况: [前驱][新插入元素][后继][后继的后继]...[最后一个元素][END] 在这种情况下, 如果[后继]被扩展了, 那么"最后一个元素"的偏移量是需要加上一个nextdiff的, 也就是这个if块中所做的事情 如果后继没有被扩展, 累加一个为0的nextdiff也是正确的 第二种情况: [前驱][新插入元素][后继][END] 在这种情况下, 如果[后继]被扩展了, 因为后继本身就是最后一个元素, 所以ziplist的“表尾元素偏移量”也不需要更新 第三种情况: [前驱][新插入元素][END] 在这种情况下, ziplist的“表尾元素偏移量”需要通过下面的代码计算, 就是当前if块外的if块配对的else块 */ } } else { ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p - zl); } if(nextdiff != 0) { // 判断nextdiff是否为0, 如果不为0, 则说明当前元素的插入导致了后继元素的扩展 offset = p - zl; // 用offset保存ziplist头地址到当前插入元素的偏移量 zl = __ziplistCascadeUpdate(zl, p+reqlen); // 后继元素的扩展可能会导致链锁扩展, 用__ziplistCascadeUpdate函数来处理这种情况, __ziplistCascadeUpdate函数可能会引起ziplist的重新分配 // 这就是为什么要保存offset的原因 p = zl + offset; // 让p重新指向新插入元素的位置 } // 至此 // ziplist扩容完毕, 并且正确的处理了元素的迁移, 以及可能发生的链锁反应 // p指向为新插入元素预留的空间地址 // 新元素的内容还未写入ziplist // 显然, 接下来的任务就是将新元素的相关数据写入p所指向的内存空间 p += zipPrevEncodeLength(p, prevlen); // 写入“前驱元素所占用长度”字段, 并向后移动p, 使其指向编码字段 p += zipEncodeLength(p, encoding, slen); // 写入“编码”字段, 并向后移动p, 使其指向“内容”字段 if(ZIP_IS_STR(encoding)) { // 判断编码方式是否为字符串编码, 如果是 memcpy(p, s, slen); // 直接调用memcpy写入内容 } else { // 如果不是 zipSaveInteger(p, value, encoding); // 调用zipSaveInteger将整数值写入 } ZIPLIST_INCR_LENGTH(zl, 1); // 更新ziplist中结点的数量 return zl; } /* 新建一个ziplist [10字节头][1字节尾标记] [0000000B][0000000A][00][FF] */ 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; } /* 向ziplist中添加一个元素, 添加的位置只能是头部或者尾部 参数: zl 目标ziplist的地址 s 待添加的新元素的内容的地址 slen 新元素占用的字节数 where 添加元素的位置(头还是尾, 取值应为ZIPLIST_HEAD或ZIPLIST_TAIL之一) */ unsigned char * ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) { unsigned char * p; // 若添加位置为头部, 则p指向第一个元素, 若表中无元素, p将指向结束标记 // 若添加位置为尾部, 则p指向结束标记 p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl); // 调用内部函数__ziplistInsert完成插入 return __ziplistInsert(zl, p, s, slen); } /* 返回一个整数编码元素中的值 */ static int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) { int16_t i16; int32_t i32; int64_t i64; int64_t ret = 0; if(encoding == ZIP_INT_8B) { ret = ((int8_t *)p)[0]; } else if(encoding == ZIP_INT_16B) { memcpy(&i16, p, sizeof(i16)); memrev16ifbe(&i16); ret = i16; } else if(encoding == ZIP_INT_24B) { i32 = 0; memcpy(((uint8_t *)&i32)+1, p, sizeof(i32)-sizeof(uint8_t)); memrev32ifbe(&i32); ret = i32>>8; } else if(encoding == ZIP_INT_32B) { memcpy(&i32, p, sizeof(i32)); memrev32ifbe(&i32); ret = i32; } else if(encoding == ZIP_INT_64B) { memcpy(&i64, p, sizeof(i64)); memrev64ifbe(&i64); ret = i64; } else if(encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX) { ret = (encoding & ZIP_INT_IMM_MASK) - 1; } else { assert(NULL); } return ret; } /* 返回一个元素的地址, 该返回的地址应该用于ziplistNext进行下一步迭代操作 当index为正数时, 代表的是元素的位置, 0为正向第一个元素, 1为正向第二个元素 当index为负数时, 代表的是元素的位置, -1为反向第一个元素, -2为反向第二个元素 */ unsigned char * ziplistIndex(unsigned char * zl, int index) { unsigned char * p; zlentry entry; if(index < 0) { // 如果index<0, 则向前遍历 index = (-index)-1; // 把index转化为正数, 该正数表示“目的结点距离表尾结点的距离, 单位为结点数” p = ZIPLIST_ENTRY_TAIL(zl); // 先获取表尾结点 if(p[0] != ZIP_END) { entry = zipEntry(p); while(entry.prevrawlen > 0 && index--) { p -= entry.prevrawlen; entry = zipEntry(p); } } } else { // 如果index>0, 向后遍历 p = ZIPLIST_ENTRY_HEAD(zl); while(p[0] != ZIP_END && index--) { p += zipRawEntryLength(p); } } return (p[0] == ZIP_END || index > 0) ? NULL : p; } /* 返回p所指向元素的下一个元素 */ unsigned char * ziplistNext(unsigned char *zl, unsigned char *p) { ((void) zl); // 并没有使用参数zl, 避免编译警告 if(p[0] == ZIP_END) { return NULL; } p += zipRawEntryLength(p); if(p[0] == ZIP_END) { return NULL; } return p; } /* 返回p所指向元素的上一个元素 */ unsigned char * ziplistPrev(unsigned char *zl, unsigned char *p) { zlentry entry; if(p[0] == ZIP_END) { p = ZIPLIST_ENTRY_TAIL(zl); return (p[0] == ZIP_END) ? NULL : p; } else if(p == ZIPLIST_ENTRY_HEAD(zl)) { return NULL; } else { entry = zipEntry(p); assert(entry.prevrawlen > 0); return p - entry.prevrawlen; } } /* 获取一个结点中的值 如果结点是字符串编码的, 则将结点中的内容地址赋值给sstr(浅拷贝) 如果结点是整数编码的, 则将数值存储在sval中(值拷贝) */ unsigned int ziplistGet(unsigned char *p, unsigned char **sstr, unsigned int *slen, long long *sval) { zlentry entry; if(p == NULL || p[0] == ZIP_END) return 0; if(sstr) *sstr = NULL; entry = zipEntry(p); if(ZIP_IS_STR(entry.encoding)) { if(sstr) { *slen = entry.len; *sstr = p+entry.headersize; } } else { if(sval) { *sval = zipLoadInteger(p+entry.headersize, entry.encoding); // 如果是整数编码的, 则调用zipLoadInteger函数取得整数值 } } return 1; } /* 插入新元素 参数: zl 被插入的ziplist的地址 p 插入位置 s 要被插入的元素的内容的地址 slen 要被插入的元素的内容的字节长度 */ unsigned char * ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) { return __ziplistInsert(zl, p, s, slen); } /* 删除指定位置上的元素 参数: zl ziplist的地址 p 这是一个二级指针, *p应该指向被删除的元素的首地址, 且函数运行完毕后, *p会被更新 即若元素为 [A][B][C][D] 删除时*p 指向 [C] 那么删除之后, 表中元素将变成[A][B][D], 且*p的值会被更新, 新值将指向[D] */ unsigned char * ziplistDelete(unsigned char *zl, unsigned char **p) { size_t offset = *p - zl; // 计算被删除元素距离表头的偏移量 zl = __ziplistDelete(zl, *p, 1); // 删除元素 *p = zl + offset; // 更新*p return zl; } /* 删除ziplist中索引为index的结点后的num个结点, 包括索引为index的结点本身 */ unsigned char * ziplistDeleteRange(unsigned char *zl, unsigned int index, unsigned int num) { unsigned char * p = ziplistIndex(zl, index); return (p == NULL) ? zl : __ziplistDelete(zl, p, num); } /* 比较结点中的内容, 相同返回0, 否则返回非0值 参数: p 应指向ziplist中的一个元素的首地址 sstr 应该指向一个元素的内容的首地址 slen sstr作为元素内容的字节长度 这个函数假定sstr是指向一个类似于元素内容区的地址 而sstr本身并不包含编码方式信息 编码方式以p所指向的元素的编码方式为准进行比较 */ unsigned int ziplistCompare(unsigned char *p, unsigned char *sstr, unsigned int slen) { zlentry entry; unsigned char sencoding; long long zval, sval; if(p[0] == ZIP_END) return 0; entry = zipEntry(p); if(ZIP_IS_STR(entry.encoding)) { // 在p以字符串编码的情况下, 直接比较内存字节 if(entry.len == slen) { return memcmp(p+entry.headersize, sstr, slen) == 0; } else { return 0; } } else { // 在p以整数编码的情况下, 比较整数的值 if(zipTryEncoding(sstr, slen, &sval, &sencoding)) { zval = zipLoadInteger(p+entry.headersize, entry.encoding); return zval == sval; } } return 0; } /* 给出一个数据区vstr与该数据区的长度vlen, 在ziplist中, 以p开始的元素处向后每隔skip个元素进行比较搜索 直到找到某个元素的内容与vstr中的内容一致时, 运行结束, 返回该元素的地址 参数: p 指向某个ziplist中的元素, 比较搜索从此开始 vstr 数据区指针 vlen 数据区长度 skip 比较搜索的跳过结点数目。 例如ziplist如右: [a][b][c][d][e][f][g][h][i] p指向[b], 跳过数为2 则将先后比较[b][e][h]这三个元素是否和vstr数据区中的数据相等 当跳过数为0时, 将比较从[b]~[i]的所有元素 当路过数>=7时, 只比较[b]一个元素 这个函数的设计有点迷啊。。。 */ unsigned char * ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip) { int skipcnt = 0; unsigned char vencoding = 0; long long vll = 0; while(p[0] != ZIP_END) { // 遍历从p开始直到结束标记的所有结点 unsigned int prevlensize; unsigned int encoding; unsigned int lensize; unsigned int len; unsigned char * q; ZIP_DECODE_PREVLENSIZE(p, prevlensize); // 赋值prevlensize, encoding, lensize, len四个变量 ZIP_DECODE_LENGTH(p+prevlensize, encoding, lensize, len); q = p + prevlensize + lensize; // q指向p元素的数据区, 注意lensize是 if(skipcnt == 0) { // 若跳过数为0, 则进行比较 if(ZIP_IS_STR(encoding)) { // 若p为字符串编码 if(len == vlen && memcmp(q, vstr, vlen) == 0) { // 则直接比较vstr与数据区的内存 return p; } } else { // 若p是整数编码 if(vencoding == 0) { // 如果vencoding为0, 即之前没有尝试将vstr中的内容解释为整数 if(!zipTryEncoding(vstr, vlen, &vll, &vencoding)) { // 就强行解释一波, 并把所需的编码方式写进vencoding中 vencoding = UCHAR_MAX; // 如果解释失败, 说明vstr中的值并不能看做是整数, 则把vencoding设置为UCHAR_MAX, 下次看到UCHAR_MAX的时候就不需要再次尝试强行解释了 } assert(vencoding); } if(vencoding != UCHAR_MAX) { // 在vstr能被解释为整数的情况下 long long ll = zipLoadInteger(q, encoding); // 用ll存储元素数据区的整数 if(ll == vll) { // 判断ll和强行解释结点vll之间的相等关系 return p; } } } skipcnt = skip; // 每比较一次, 跳过数设置为参数期望的数值 } else { // 若跳过数不为0, 则不进行比较 skipcnt--; } p = q + len; // 将p指向下一个元素 } return NULL; } /* 求一个ziplist中结点的数量 */ unsigned int ziplistLen(unsigned char *zl) { unsigned int len = 0; if(intrev16ifbe(ZIPLIST_LENGTH(zl)) < UINT16_MAX) { // 先看结点数量字段, 如果小于65535, 则直接返回这个字段的值 len = intrev16ifbe(ZIPLIST_LENGTH(zl)); } else { // 如果>=65535, 则需要手动遍历整个ziplist unsigned char * p = zl + ZIPLIST_HEADER_SIZE; while(*p != ZIP_END) { p += zipRawEntryLength(p); len++; } if(len < UINT16_MAX) // 如果主动遍历后的数目小于65535, 那么就说明原“结点数量”字段是错误的, 更正之(这是一个在正常情况下不会走到的判断分支) ZIPLIST_LENGTH(zl) = intrev16ifbe(len); } return len; } /* 求一个ziplist占用的总字节数 */ size_t ziplistBlobLen(unsigned char * zl) { return intrev32ifbe(ZIPLIST_BYTES(zl)); // 直接调用宏ZIPLIST_BYTES即可, 即直接返回“字节长度”字段即可 }
标签:
原文地址:http://www.cnblogs.com/neooelric/p/5628380.html