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的主要内容,如果未能解决你的问题,请参考以下文章

Redis2.6源代码走读第004课:字典的实现02

Redis2.6源代码走读第004课:字典的实现03

Redis2.6源代码走读第002课:简单动态字符串01

使用IDEA学习JSP代码第007课

2019-08-19,beego代码走读,二、请求入口

Python 中的列表范围 - Project Euler 007