码迷,mamicode.com
首页 > 其他好文 > 详细

Redis2.6源代码走读第007课:压缩列表02

时间:2016-06-29 23:30:50      阅读:171      评论:0      收藏:0      [点我收藏+]

标签:

身体被掏空了一星期, 前天终于挣扎着继续做这个代码走读

不得不说, 压缩列表的实现复杂程度还是超出了我的预计

破天荒的第一次, 我必须手动上注释, 才能防止自己迷失在代码里面。

 

今天还没有录制视频, 最近一直在做公司的事, 时间也比较紧, 所以今天只是把我经过注释的压缩列表部分的代码贴出来, 同时给出一个阅读建议

在学习压缩列表的过程中, 我也参考了黄健宏先生对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即可, 即直接返回“字节长度”字段即可
}

 

Redis2.6源代码走读第007课:压缩列表02

标签:

原文地址:http://www.cnblogs.com/neooelric/p/5628380.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!