Redis2.6源代码走读第007课:压缩列表02
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis2.6源代码走读第007课:压缩列表02相关的知识,希望对你有一定的参考价值。
身体被掏空了一星期, 前天终于挣扎着继续做这个代码走读
不得不说, 压缩列表的实现复杂程度还是超出了我的预计
破天荒的第一次, 我必须手动上注释, 才能防止自己迷失在代码里面。
今天还没有录制视频, 最近一直在做公司的事, 时间也比较紧, 所以今天只是把我经过注释的压缩列表部分的代码贴出来, 同时给出一个阅读建议
在学习压缩列表的过程中, 我也参考了黄健宏先生对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: 以上是关于Redis2.6源代码走读第007课:压缩列表02的主要内容,如果未能解决你的问题,请参考以下文章