zoukankan      html  css  js  c++  java
  • Redis学习之ziplist压缩列表源码分析

    一.压缩列表ziplist在redis中的应用

    1.做列表键

    当一个列表键只包含少量列表项,并且每个列表项要么是小整数,要么是短字符串,那么redis会使用压缩列表作为列表键的底层实现

    2.哈希键

    当一个哈希键只包含少量的键值对,并且每个键值对的键和值要么是小整数,要么是短字符串,那么redis会使用压缩列表作为哈希键的底层实现

     

    二.压缩列表的定义:

    压缩列表ziplist是redis为了节约内存而开发的,是由一些了特殊编码的连续内存块组成的顺序数据结构

     

    三.关于压缩列表的连锁更新

    更新的是什么?

    更新的是每个结点的previous_entry_length以及结点的内存中的位置,涉及到内存空间的重分配

    连锁更新发生的情况有哪些?

    比如在一个ziplist中,有多个连续的,长度介于250字节到253字节之间的结点e1到eN,因为e1到eN的所有结点都小于254字节,所以记录这些结点的privious_entry_length属性仅仅需要一个字节,这个时候如果我们将一个长度大于254字节的新结点插入到ziplist的头部,那么e1到eN结点是所有privoius_entry_length属性所占字节要从1字节变成5个字节,这里面涉及到对所有结点内存空间的重新分配,在这种最坏的情况下,需要对ziplist执行N次的空间重新分配操作,每次空间重分配操作的复杂度为O(N),所以连锁更新最坏的情况下,复杂度为O(N*N)

    尽管连锁更新的复杂度高,但是真正造成性能问题的概率还是很低的,因为连锁更新的极端情况并不少见,在结点数量少的时候,即使出现连锁更新,也不会对性能造成影响,基于以上原因

    ziplist的平均复杂度仅为O(N)

    源码分析:

    ziplist.h

    #define ZIPLIST_HEAD 0
    #define ZIPLIST_TAIL 1
    
    /*
    前置说明:
    
    ziplist定义成了宏属性,在ziplist.c文件中
    ziplist的结点zlentry的结构体也在ziplist.c文件中
    看不懂为什么这么操作.....好迷啊
    
    */
    
    //=========  API的定义  =====================//
    
    //创建一个新的压缩列表
    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 *s, 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);

    ziplist.c

    /*
     * Ziplist 是为了尽可能地节约内存而设计的特殊编码双端链表。
     *
     * Ziplist 可以储存字符串值和整数值,
     * 其中,整数值被保存为实际的整数,而不是字符数组。
     *
     * Ziplist 允许在列表的两端进行 O(1) 复杂度的 push 和 pop 操作。
     * 但是,因为这些操作都需要对整个 ziplist 进行内存重分配,
     * 所以实际的复杂度和 ziplist 占用的内存大小有关。
     *-------------------------------------------------------------------------------------------------------------------------------------------------------------
     * Ziplist 的整体布局:
     *
     * 以下是 ziplist 的一般布局:
     *
     * <zlbytes><zltail><zllen><entry><entry><zlend>
     *
     * <zlbytes> 是一个无符号整数,保存着 ziplist 使用的内存数量。
     * 通过这个值,程序可以直接对 ziplist 的内存大小进行调整,
     * 而无须为了计算 ziplist 的内存大小而遍历整个列表。
     *
     * <zltail> 保存着到达列表中最后一个节点的偏移量。
     * 这个偏移量使得对表尾的 pop 操作可以在无须遍历整个列表的情况下进行。
     *
     * <zllen> 保存着列表中的节点数量。
     * 当 zllen 保存的值大于 2**16-2 时,
     * 程序需要遍历整个列表才能知道列表实际包含了多少个节点。
     *
     * <zlend> 的长度为 1 字节,值为 255 ,标识列表的末尾。
     *
     * ZIPLIST ENTRIES:
     * ZIPLIST 节点:
     *
     * 每个 ziplist 节点的前面都带有一个 header ,这个 header 包含两部分信息:
     * 1)前置节点的长度,在程序从后向前遍历时使用。
     * 2)当前节点所保存的值的类型和长度。
     *
     * 编码前置节点的长度的方法如下:
     *
     * 1) 如果前置节点的长度小于 254 字节,那么程序将使用 1 个字节来保存这个长度值。
     *
     * 2) 如果前置节点的长度大于等于 254 字节,那么程序将使用 5 个字节来保存这个长度值:
     *    a) 第 1 个字节的值将被设为 254 ,用于标识这是一个 5 字节长的长度值。
     *    b) 之后的 4 个字节则用于保存前置节点的实际长度。
     *
     * header 另一部分的内容和节点所保存的值有关。
     *
     * 1) 如果节点保存的是字符串值,
     *    那么这部分 header 的头 2 个位将保存编码字符串长度所使用的类型,
     *    而之后跟着的内容则是字符串的实际长度。
     *
     * |00pppppp| - 1 byte
     *      String value with length less than or equal to 63 bytes (6 bits).
     *      字符串的长度小于或等于 63 字节。
     * |01pppppp|qqqqqqqq| - 2 bytes
     *      String value with length less than or equal to 16383 bytes (14 bits).
     *      字符串的长度小于或等于 16383 字节。
     * |10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes
     *      String value with length greater than or equal to 16384 bytes.
     *      字符串的长度大于或等于 16384 字节。
     *
     * 2) 如果节点保存的是整数值,
     *    那么这部分 header 的头 2 位都将被设置为 1 ,
     *    而之后跟着的 2 位则用于标识节点所保存的整数的类型。
     *
     * |11000000| - 1 byte
     *      Integer encoded as int16_t (2 bytes).
     *      节点的值为 int16_t 类型的整数,长度为 2 字节。
     * |11010000| - 1 byte
     *      Integer encoded as int32_t (4 bytes).
     *      节点的值为 int32_t 类型的整数,长度为 4 字节。
     * |11100000| - 1 byte
     *      Integer encoded as int64_t (8 bytes).
     *      节点的值为 int64_t 类型的整数,长度为 8 字节。
     * |11110000| - 1 byte
     *      Integer encoded as 24 bit signed (3 bytes).
     *      节点的值为 24 位(3 字节)长的整数。
     * |11111110| - 1 byte
     *      Integer encoded as 8 bit signed (1 byte).
     *      节点的值为 8 位(1 字节)长的整数。
     * |1111xxxx| - (with xxxx between 0000 and 1101) immediate 4 bit integer.
     *      Unsigned integer from 0 to 12. The encoded value is actually from
     *      1 to 13 because 0000 and 1111 can not be used, so 1 should be
     *      subtracted from the encoded 4 bit value to obtain the right value.
     *      节点的值为介于 0 至 12 之间的无符号整数。
     *      因为 0000 和 1111 都不能使用,所以位的实际值将是 1 至 13 。
     *      程序在取得这 4 个位的值之后,还需要减去 1 ,才能计算出正确的值。
     *      比如说,如果位的值为 0001 = 1 ,那么程序返回的值将是 1 - 1 = 0 。
     * |11111111| - End of ziplist.
     *      ziplist 的结尾标识
     *
     *
     * 所有整数都表示为小端字节序。
     *
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdint.h>
    #include <limits.h>
    #include "zmalloc.h"
    #include "util.h"
    #include "ziplist.h"
    #include "endianconv.h"
    #include "redisassert.h"
    
    /*
     * ziplist 末端标识符,以及 5 字节长长度标识符
     */
    #define ZIP_END 255
    #define ZIP_BIGLEN 254
    
    /*
     * 字符串编码和整数编码的掩码
     */
    #define ZIP_STR_MASK 0xc0
    #define ZIP_INT_MASK 0x30
    
    /*
     * 字符串编码类型
     */
    #define ZIP_STR_06B (0 << 6)
    #define ZIP_STR_14B (1 << 6)
    #define ZIP_STR_32B (2 << 6)
    
    /*
     * 整数编码类型
     */
    #define ZIP_INT_16B (0xc0 | 0<<4)
    #define ZIP_INT_32B (0xc0 | 1<<4)
    #define ZIP_INT_64B (0xc0 | 2<<4)
    #define ZIP_INT_24B (0xc0 | 3<<4)
    #define ZIP_INT_8B 0xfe
    
    /*
     * 4 位整数编码的掩码和类型
     */
    #define ZIP_INT_IMM_MASK 0x0f
    #define ZIP_INT_IMM_MIN 0xf1    /* 11110001 */
    #define ZIP_INT_IMM_MAX 0xfd    /* 11111101 */
    #define ZIP_INT_IMM_VAL(v) (v & ZIP_INT_IMM_MASK)
    
    /*
     * 24 位整数的最大值和最小值
     */
    #define INT24_MAX 0x7fffff
    #define INT24_MIN (-INT24_MAX - 1)
    
    /*
     * 查看给定编码 enc 是否字符串编码
     */
    #define ZIP_IS_STR(enc) (((enc) & ZIP_STR_MASK) < ZIP_STR_MASK)
    
    /*
     * ziplist 属性宏
     */
    // 定位到 ziplist 的 bytes 属性,该属性记录了整个 ziplist 所占用的内存字节数
    // 用于取出 bytes 属性的现有值,或者为 bytes 属性赋予新值
    #define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))
    // 定位到 ziplist 的 offset 属性,该属性记录了到达表尾节点的偏移量
    // 用于取出 offset 属性的现有值,或者为 offset 属性赋予新值
    #define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
    // 定位到 ziplist 的 length 属性,该属性记录了 ziplist 包含的节点数量
    // 用于取出 length 属性的现有值,或者为 length 属性赋予新值
    #define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
    // 返回 ziplist 表头的大小
    #define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))
    // 返回指向 ziplist 第一个节点(的起始位置)的指针
    #define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)
    // 返回指向 ziplist 最后一个节点(的起始位置)的指针
    #define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
    // 返回指向 ziplist 末端 ZIP_END (的起始位置)的指针
    #define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)
    
    /*
    空白 ziplist 示例图
    
    area        |<---- ziplist header ---->|<-- end -->|
    
    size          4 bytes   4 bytes 2 bytes  1 byte
                +---------+--------+-------+-----------+
    component   | zlbytes | zltail | zllen | zlend     |
                |         |        |       |           |
    value       |  1011   |  1010  |   0   | 1111 1111 |
                +---------+--------+-------+-----------+
                                           ^
                                           |
                                   ZIPLIST_ENTRY_HEAD
                                           &
    address                        ZIPLIST_ENTRY_TAIL
                                           &
                                   ZIPLIST_ENTRY_END
    
    非空 ziplist 示例图
    
    area        |<---- ziplist header ---->|<----------- entries ------------->|<-end->|
    
    size          4 bytes  4 bytes  2 bytes    ?        ?        ?        ?     1 byte
                +---------+--------+-------+--------+--------+--------+--------+-------+
    component   | zlbytes | zltail | zllen | entry1 | entry2 |  ...   | entryN | zlend |
                +---------+--------+-------+--------+--------+--------+--------+-------+
                                           ^                          ^        ^
    address                                |                          |        |
                                    ZIPLIST_ENTRY_HEAD                |   ZIPLIST_ENTRY_END
                                                                      |
                                                            ZIPLIST_ENTRY_TAIL
    */
    
    /*
     * 增加 ziplist 的节点数
     *
     * T = O(1)
     */
    #define ZIPLIST_INCR_LENGTH(zl,incr) { 
        if (ZIPLIST_LENGTH(zl) < UINT16_MAX) 
            ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))+incr); 
    }
    
    /*
     * 保存 ziplist 节点信息的结构
     */
    typedef struct zlentry
    {
    
        //前置节点的长度
        unsigned int prevrawlen;
    
        //编码 prevrawlen 所需的字节大小
        unsigned int prevrawlensize;
    
        //当前节点值的长度
        unsigned int len;
    
        //编码 len 所需的字节大小
        unsigned int lensize
    
        // 当前节点 header 的大小,等于 prevrawlensize + lensize
        unsigned int headersize;
    
        // 当前节点值所使用的编码类型
        unsigned char encoding;
    
        // 指向当前节点的指针
        unsigned char *p;
    
    } zlentry;
    
    /*
     * 从 ptr 中取出节点值的编码类型,并将它保存到 encoding 变量中。
     *
     * T = O(1)
     */
    #define ZIP_ENTRY_ENCODING(ptr, encoding) do {  
        (encoding) = (ptr[0]); 
        if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; 
    } while(0)
    
    /*
     * 返回保存 encoding 编码的值所需的字节数量
     *
     * T = O(1)
     */
    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 bit immediate */
        }
    
        assert(NULL);
        return 0;
    }
    
    /*
     * 编码节点长度值 l ,并将它写入到 p 中,然后返回编码 l 所需的字节数量。
     *
     * 如果 p 为 NULL ,那么仅返回编码 l 所需的字节数量,不进行写入。
     *
     * T = O(1)
     */
    static unsigned int zipEncodeLength(unsigned char *p, unsigned char encoding, unsigned int rawlen)
    {
        unsigned char len = 1, buf[5];
    
        // 编码字符串
        if (ZIP_IS_STR(encoding))
        {
            /* Although encoding is given it may not be set for strings,
             * so we determine it here using the raw length. */
            if (rawlen <= 0x3f)
            {
                if (!p) return len;
                buf[0] = ZIP_STR_06B | rawlen;
            }
            else if (rawlen <= 0x3fff)
            {
                len += 1;
                if (!p) return len;
                buf[0] = ZIP_STR_14B | ((rawlen >> 8) & 0x3f);
                buf[1] = rawlen & 0xff;
            }
            else
            {
                len += 4;
                if (!p) return len;
                buf[0] = ZIP_STR_32B;
                buf[1] = (rawlen >> 24) & 0xff;
                buf[2] = (rawlen >> 16) & 0xff;
                buf[3] = (rawlen >> 8) & 0xff;
                buf[4] = rawlen & 0xff;
            }
    
            // 编码整数
        }
        else
        {
            /* Implies integer encoding, so length is always 1. */
            if (!p) return len;
            buf[0] = encoding;
        }
    
        /* Store this length at p */
        // 将编码后的长度写入 p
        memcpy(p,buf,len);
    
        // 返回编码所需的字节数
        return len;
    }
    
    /*
     * 解码 ptr 指针,取出列表节点的相关信息,并将它们保存在以下变量中:
     *
     * - encoding 保存节点值的编码类型。
     *
     * - lensize 保存编码节点长度所需的字节数。
     *
     * - len 保存节点的长度。
     *
     * T = O(1)
     */
    #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);
    
    /*
     * 对前置节点的长度 len 进行编码,并将它写入到 p 中,
     * 然后返回编码 len 所需的字节数量。
     *
     * 如果 p 为 NULL ,那么不进行写入,仅返回编码 len 所需的字节数量。
     *
     * T = O(1)
     */
    static unsigned int zipPrevEncodeLength(unsigned char *p, unsigned int len)
    {
    
        // 仅返回编码 len 所需的字节数量
        if (p == NULL)
        {
            return (len < ZIP_BIGLEN) ? 1 : sizeof(len)+1;
    
            // 写入并返回编码 len 所需的字节数量
        }
        else
        {
    
            // 1 字节
            if (len < ZIP_BIGLEN)
            {
                p[0] = len;
                return 1;
    
                // 5 字节
            }
            else
            {
                // 添加 5 字节长度标识
                p[0] = ZIP_BIGLEN;
                // 写入编码
                memcpy(p+1,&len,sizeof(len));
                // 如果有必要的话,进行大小端转换
                memrev32ifbe(p+1);
                // 返回编码长度
                return 1+sizeof(len);
            }
        }
    }
    
    /*
     *
     * 将原本只需要 1 个字节来保存的前置节点长度 len 编码至一个 5 字节长的 header 中。
     *
     * T = O(1)
     */
    static void zipPrevEncodeLengthForceLarge(unsigned char *p, unsigned int len)
    {
    
        if (p == NULL) return;
    
    
        // 设置 5 字节长度标识
        p[0] = ZIP_BIGLEN;
    
        // 写入 len
        memcpy(p+1,&len,sizeof(len));
        memrev32ifbe(p+1);
    }
    
    /*
     * 解码 ptr 指针,
     * 取出编码前置节点长度所需的字节数,并将它保存到 prevlensize 变量中。
     *
     * T = O(1)
     */
    #define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do {                          
        if ((ptr)[0] < ZIP_BIGLEN) {                                               
            (prevlensize) = 1;                                                     
        } else {                                                                   
            (prevlensize) = 5;                                                     
        }                                                                          
    } while(0);
    
    /*
     * 解码 ptr 指针,
     * 取出编码前置节点长度所需的字节数,
     * 并将这个字节数保存到 prevlensize 中。
     *
     * 然后根据 prevlensize ,从 ptr 中取出前置节点的长度值,
     * 并将这个长度值保存到 prevlen 变量中。
     *
     * T = O(1)
     */
    #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);
    
    /*
     * 计算编码新的前置节点长度 len 所需的字节数,
     * 减去编码 p 原来的前置节点长度所需的字节数之差。
     *
     * T = O(1)
     */
    static int zipPrevLenByteDiff(unsigned char *p, unsigned int len)
    {
        unsigned int prevlensize;
    
        // 取出编码原来的前置节点长度所需的字节数
        // T = O(1)
        ZIP_DECODE_PREVLENSIZE(p, prevlensize);
    
        // 计算编码 len 所需的字节数,然后进行减法运算
        // T = O(1)
        return zipPrevEncodeLength(NULL, len) - prevlensize;
    }
    
    /*
     * 返回指针 p 所指向的节点占用的字节数总和。
     *
     * T = O(1)
     */
    static unsigned int zipRawEntryLength(unsigned char *p)
    {
        unsigned int prevlensize, encoding, lensize, len;
    
        // 取出编码前置节点的长度所需的字节数
        // T = O(1)
        ZIP_DECODE_PREVLENSIZE(p, prevlensize);
    
        // 取出当前节点值的编码类型,编码节点值长度所需的字节数,以及节点值的长度
        // T = O(1)
        ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);
    
        // 计算节点占用的字节数总和
        return prevlensize + lensize + len;
    }
    
    /* Check if string pointed to by 'entry' can be encoded as an integer.
     * Stores the integer value in 'v' and its encoding in 'encoding'.
     *
     * 检查 entry 中指向的字符串能否被编码为整数。
     *
     * 如果可以的话,
     * 将编码后的整数保存在指针 v 的值中,并将编码的方式保存在指针 encoding 的值中。
     *
     * 注意,这里的 entry 和前面代表节点的 entry 不是一个意思。
     *
     * T = O(N)
     */
    static int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding)
    {
        long long value;
    
        // 忽略太长或太短的字符串
        if (entrylen >= 32 || entrylen == 0) return 0;
    
        // 尝试转换
        // T = O(N)
        if (string2ll((char*)entry,entrylen,&value))
        {
    
            /* Great, the string can be encoded. Check what's the smallest
             * of our encoding types that can hold this 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;
            }
    
            // 记录值到指针
            *v = value;
    
            // 返回转换成功标识
            return 1;
        }
    
        // 转换失败
        return 0;
    }
    
    /* Store integer 'value' at 'p', encoded as 'encoding'
     *
     * 以 encoding 指定的编码方式,将整数值 value 写入到 p 。
     *
     * T = O(1)
     */
    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)
        {
            ((int8_t*)p)[0] = (int8_t)value;
        }
        else if (encoding == ZIP_INT_16B)
        {
            i16 = value;
            memcpy(p,&i16,sizeof(i16));
            memrev16ifbe(p);
        }
        else if (encoding == ZIP_INT_24B)
        {
            i32 = value<<8;
            memrev32ifbe(&i32);
            memcpy(p,((uint8_t*)&i32)+1,sizeof(i32)-sizeof(uint8_t));
        }
        else if (encoding == ZIP_INT_32B)
        {
            i32 = value;
            memcpy(p,&i32,sizeof(i32));
            memrev32ifbe(p);
        }
        else if (encoding == ZIP_INT_64B)
        {
            i64 = value;
            memcpy(p,&i64,sizeof(i64));
            memrev64ifbe(p);
        }
        else if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX)
        {
            /* Nothing to do, the value is stored in the encoding itself. */
        }
        else
        {
            assert(NULL);
        }
    }
    
    /* Read integer encoded as 'encoding' from 'p'
     *
     * 以 encoding 指定的编码方式,读取并返回指针 p 中的整数值。
     *
     * T = O(1)
     */
    static int64_t zipLoadInteger(unsigned char *p, unsigned char encoding)
    {
        int16_t i16;
        int32_t i32;
        int64_t i64, 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_32B)
        {
            memcpy(&i32,p,sizeof(i32));
            memrev32ifbe(&i32);
            ret = i32;
        }
        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_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;
    }
    
    /* Return a struct with all information about an entry.
     *
     * 将 p 所指向的列表节点的信息全部保存到 zlentry 中,并返回该 zlentry 。
     *
     * T = O(1)
     */
    static zlentry zipEntry(unsigned char *p)
    {
        zlentry e;
    
        // e.prevrawlensize 保存着编码前一个节点的长度所需的字节数
        // e.prevrawlen 保存着前一个节点的长度
        // T = O(1)
        ZIP_DECODE_PREVLEN(p, e.prevrawlensize, e.prevrawlen);
    
        // p + e.prevrawlensize 将指针移动到列表节点本身
        // e.encoding 保存着节点值的编码类型
        // e.lensize 保存着编码节点值长度所需的字节数
        // e.len 保存着节点值的长度
        // T = O(1)
        ZIP_DECODE_LENGTH(p + e.prevrawlensize, e.encoding, e.lensize, e.len);
    
        // 计算头结点的字节数
        e.headersize = e.prevrawlensize + e.lensize;
    
        // 记录指针
        e.p = p;
    
        return e;
    }
    
    /* Create a new empty ziplist.
     *
     * 创建并返回一个新的 ziplist
     *
     * T = O(1)
     */
    unsigned char *ziplistNew(void)
    {
    
        // ZIPLIST_HEADER_SIZE 是 ziplist 表头的大小
        // 1 字节是表末端 ZIP_END 的大小
        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;
    }
    
    
    /* Resize the ziplist.
     *
     * 调整 ziplist 的大小为 len 字节。
     *
     * 当 ziplist 原有的大小小于 len 时,扩展 ziplist 不会改变 ziplist 原有的元素。
     *
     * T = O(N)
     */
    static unsigned char *ziplistResize(unsigned char *zl, unsigned int len)
    {
    
        // 用 zrealloc ,扩展时不改变现有元素
        zl = zrealloc(zl,len);
    
        // 更新 bytes 属性
        ZIPLIST_BYTES(zl) = intrev32ifbe(len);
    
        // 重新设置表末端
        zl[len-1] = ZIP_END;
    
        return zl;
    }
    
    
    /*
     * 连锁更新
     *
     * 当将一个新节点添加到某个节点之前的时候,
     * 如果原节点的 header 空间不足以保存新节点的长度,
     * 那么就需要对原节点的 header 空间进行扩展(从 1 字节扩展到 5 字节)。
     *
     * 但是,当对原节点进行扩展之后,原节点的下一个节点的 prevlen 可能出现空间不足,
     * 这种情况在多个连续节点的长度都接近 ZIP_BIGLEN 时可能发生。
     *
     * 这个函数就用于检查并修复后续节点的空间问题。
     *
     * 反过来说,
     * 因为节点的长度变小而引起的连续缩小也是可能出现的,
     * 不过,为了避免扩展-缩小-扩展-缩小这样的情况反复出现(flapping,抖动),
     * 我们不处理这种情况,而是任由 prevlen 比所需的长度更长。
     *
     * 注意,程序的检查是针对 p 的后续节点,而不是 p 所指向的节点。
     * 因为节点 p 在传入之前已经完成了所需的空间扩展工作。
     *
     * T = O(N^2)
     */
    static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p)
    {
        size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), rawlen, rawlensize;
        size_t offset, noffset, extra;
        unsigned char *np;
        zlentry cur, next;
    
        // T = O(N^2)
        while (p[0] != ZIP_END)
        {
    
            // 将 p 所指向的节点的信息保存到 cur 结构中
            cur = zipEntry(p);
            // 当前节点的长度
            rawlen = cur.headersize + cur.len;
            // 计算编码当前节点的长度所需的字节数
            // T = O(1)
            rawlensize = zipPrevEncodeLength(NULL,rawlen);
    
            /* Abort if there is no next entry. */
            // 如果已经没有后续空间需要更新了,跳出
            if (p[rawlen] == ZIP_END) break;
    
            // 取出后续节点的信息,保存到 next 结构中
            // T = O(1)
            next = zipEntry(p+rawlen);
    
            /* Abort when "prevlen" has not changed. */
            // 后续节点编码当前节点的空间已经足够,无须再进行任何处理,跳出
            // 可以证明,只要遇到一个空间足够的节点,
            // 那么这个节点之后的所有节点的空间都是足够的
            if (next.prevrawlen == rawlen) break;
    
            if (next.prevrawlensize < rawlensize)
            {
    
                /* The "prevlen" field of "next" needs more bytes to hold
                 * the raw length of "cur". */
                // 执行到这里,表示 next 空间的大小不足以编码 cur 的长度
                // 所以程序需要对 next 节点的(header 部分)空间进行扩展
    
                // 记录 p 的偏移量
                offset = p-zl;
                // 计算需要增加的节点数量
                extra = rawlensize-next.prevrawlensize;
                // 扩展 zl 的大小
                // T = O(N)
                zl = ziplistResize(zl,curlen+extra);
                // 还原指针 p
                p = zl+offset;
    
                /* Current pointer and offset for next element. */
                // 记录下一节点的偏移量
                np = p+rawlen;
                noffset = np-zl;
    
                /* Update tail offset when next element is not the tail element. */
                // 当 next 节点不是表尾节点时,更新列表到表尾节点的偏移量
                //
                // 不用更新的情况(next 为表尾节点):
                //
                // |     | next |      ==>    |     | new next          |
                //       ^                          ^
                //       |                          |
                //     tail                        tail
                //
                // 需要更新的情况(next 不是表尾节点):
                //
                // | next |     |   ==>     | new next          |     |
                //        ^                        ^
                //        |                        |
                //    old tail                 old tail
                //
                // 更新之后:
                //
                // | new next          |     |
                //                     ^
                //                     |
                //                  new tail
                // T = O(1)
                if ((zl+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) != np)
                {
                    ZIPLIST_TAIL_OFFSET(zl) =
                        intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra);
                }
    
                /* Move the tail to the back. */
                // 向后移动 cur 节点之后的数据,为 cur 的新 header 腾出空间
                //
                // 示例:
                //
                // | header | value |  ==>  | header |    | value |  ==>  | header      | value |
                //                                   |<-->|
                //                            为新 header 腾出的空间
                // T = O(N)
                memmove(np+rawlensize,
                        np+next.prevrawlensize,
                        curlen-noffset-next.prevrawlensize-1);
                // 将新的前一节点长度值编码进新的 next 节点的 header
                // T = O(1)
                zipPrevEncodeLength(np,rawlen);
    
                /* Advance the cursor */
                // 移动指针,继续处理下个节点
                p += rawlen;
                curlen += extra;
            }
            else
            {
                if (next.prevrawlensize > rawlensize)
                {
                    /* This would result in shrinking, which we want to avoid.
                     * So, set "rawlen" in the available bytes. */
                    // 执行到这里,说明 next 节点编码前置节点的 header 空间有 5 字节
                    // 而编码 rawlen 只需要 1 字节
                    // 但是程序不会对 next 进行缩小,
                    // 所以这里只将 rawlen 写入 5 字节的 header 中就算了。
                    // T = O(1)
                    zipPrevEncodeLengthForceLarge(p+rawlen,rawlen);
                }
                else
                {
                    // 运行到这里,
                    // 说明 cur 节点的长度正好可以编码到 next 节点的 header 中
                    // T = O(1)
                    zipPrevEncodeLength(p+rawlen,rawlen);
                }
    
                /* Stop here, as the raw length of "next" has not changed. */
                break;
            }
        }
    
        return zl;
    }
    
    /* Delete "num" entries, starting at "p". Returns pointer to the ziplist.
     *
     * 从位置 p 开始,连续删除 num 个节点。
     *
     * 函数的返回值为处理删除操作之后的 ziplist 。
     *
     * T = O(N^2)
     */
    static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num)
    {
        unsigned int i, totlen, deleted = 0;
        size_t offset;
        int nextdiff = 0;
        zlentry first, tail;
    
        // 计算被删除节点总共占用的内存字节数
        // 以及被删除节点的总个数
        // T = O(N)
        first = zipEntry(p);
        for (i = 0; p[0] != ZIP_END && i < num; i++)
        {
            p += zipRawEntryLength(p);
            deleted++;
        }
    
        // totlen 是所有被删除节点总共占用的内存字节数
        totlen = p-first.p;
        if (totlen > 0)
        {
            if (p[0] != ZIP_END)
            {
    
                // 执行这里,表示被删除节点之后仍然有节点存在
    
                /* Storing `prevrawlen` in this entry may increase or decrease the
                 * number of bytes required compare to the current `prevrawlen`.
                 * There always is room to store this, because it was previously
                 * stored by an entry that is now being deleted. */
                // 因为位于被删除范围之后的第一个节点的 header 部分的大小
                // 可能容纳不了新的前置节点,所以需要计算新旧前置节点之间的字节数差
                // T = O(1)
                nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);
                // 如果有需要的话,将指针 p 后退 nextdiff 字节,为新 header 空出空间
                p -= nextdiff;
                // 将 first 的前置节点的长度编码至 p 中
                // T = O(1)
                zipPrevEncodeLength(p,first.prevrawlen);
    
                /* Update offset for tail */
                // 更新到达表尾的偏移量
                // T = O(1)
                ZIPLIST_TAIL_OFFSET(zl) =
                    intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))-totlen);
    
                /* When the tail contains more than one entry, we need to take
                 * "nextdiff" in account as well. Otherwise, a change in the
                 * size of prevlen doesn't have an effect on the *tail* offset. */
                // 如果被删除节点之后,有多于一个节点
                // 那么程序需要将 nextdiff 记录的字节数也计算到表尾偏移量中
                // 这样才能让表尾偏移量正确对齐表尾节点
                // T = O(1)
                tail = zipEntry(p);
                if (p[tail.headersize+tail.len] != ZIP_END)
                {
                    ZIPLIST_TAIL_OFFSET(zl) =
                        intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
                }
    
                /* Move tail to the front of the ziplist */
                // 从表尾向表头移动数据,覆盖被删除节点的数据
                // T = O(N)
                memmove(first.p,p,
                        intrev32ifbe(ZIPLIST_BYTES(zl))-(p-zl)-1);
            }
            else
            {
    
                // 执行这里,表示被删除节点之后已经没有其他节点了
    
                /* The entire tail was deleted. No need to move memory. */
                // T = O(1)
                ZIPLIST_TAIL_OFFSET(zl) =
                    intrev32ifbe((first.p-zl)-first.prevrawlen);
            }
    
            /* Resize and update length */
            // 缩小并更新 ziplist 的长度
            offset = first.p-zl;
            zl = ziplistResize(zl, intrev32ifbe(ZIPLIST_BYTES(zl))-totlen+nextdiff);
            ZIPLIST_INCR_LENGTH(zl,-deleted);
            p = zl+offset;
    
            /* When nextdiff != 0, the raw length of the next entry has changed, so
             * we need to cascade the update throughout the ziplist */
            // 如果 p 所指向的节点的大小已经变更,那么进行级联更新
            // 检查 p 之后的所有节点是否符合 ziplist 的编码要求
            // T = O(N^2)
            if (nextdiff != 0)
                zl = __ziplistCascadeUpdate(zl,p);
        }
    
        return zl;
    }
    /* Insert item at "p". */
    /*
     * 根据指针 p 所指定的位置,将长度为 slen 的字符串 s 插入到 zl 中。
     *
     * 函数的返回值为完成插入操作之后的 ziplist
     *
     * T = O(N^2)
     */
    static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen)
    {
        // 记录当前 ziplist 的长度
        size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, prevlen = 0;
        size_t offset;
        int nextdiff = 0;
        unsigned char encoding = 0;
        long long value = 123456789; /* initialized to avoid warning. Using a value
                                        that is easy to see if for some reason
                                        we use it uninitialized. */
        zlentry entry, tail;
    
        /* Find out prevlen for the entry that is inserted. */
        if (p[0] != ZIP_END)
        {
            // 如果 p[0] 不指向列表末端,说明列表非空,并且 p 正指向列表的其中一个节点
            // 那么取出 p 所指向节点的信息,并将它保存到 entry 结构中
            // 然后用 prevlen 变量记录前置节点的长度
            // (当插入新节点之后 p 所指向的节点就成了新节点的前置节点)
            // T = O(1)
            entry = zipEntry(p);
            prevlen = entry.prevrawlen;
        }
        else
        {
            // 如果 p 指向表尾末端,那么程序需要检查列表是否为:
            // 1)如果 ptail 也指向 ZIP_END ,那么列表为空;
            // 2)如果列表不为空,那么 ptail 将指向列表的最后一个节点。
            unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
            if (ptail[0] != ZIP_END)
            {
                // 表尾节点为新节点的前置节点
    
                // 取出表尾节点的长度
                // T = O(1)
                prevlen = zipRawEntryLength(ptail);
            }
        }
    
        /* See if the entry can be encoded */
        // 尝试看能否将输入字符串转换为整数,如果成功的话:
        // 1)value 将保存转换后的整数值
        // 2)encoding 则保存适用于 value 的编码方式
        // 无论使用什么编码, reqlen 都保存节点值的长度
        // T = O(N)
        if (zipTryEncoding(s,slen,&value,&encoding))
        {
            /* 'encoding' is set to the appropriate integer encoding */
            reqlen = zipIntSize(encoding);
        }
        else
        {
            /* 'encoding' is untouched, however zipEncodeLength will use the
             * string length to figure out how to encode it. */
            reqlen = slen;
        }
        /* We need space for both the length of the previous entry and
         * the length of the payload. */
        // 计算编码前置节点的长度所需的大小
        // T = O(1)
        reqlen += zipPrevEncodeLength(NULL,prevlen);
        // 计算编码当前节点值所需的大小
        // T = O(1)
        reqlen += zipEncodeLength(NULL,encoding,slen);
    
        /* When the insert position is not equal to the tail, we need to
         * make sure that the next entry can hold this entry's length in
         * its prevlen field. */
        // 只要新节点不是被添加到列表末端,
        // 那么程序就需要检查看 p 所指向的节点(的 header)能否编码新节点的长度。
        // nextdiff 保存了新旧编码之间的字节大小差,如果这个值大于 0
        // 那么说明需要对 p 所指向的节点(的 header )进行扩展
        // T = O(1)
        nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
    
        /* Store offset because a realloc may change the address of zl. */
        // 因为重分配空间可能会改变 zl 的地址
        // 所以在分配之前,需要记录 zl 到 p 的偏移量,然后在分配之后依靠偏移量还原 p
        offset = p-zl;
        // curlen 是 ziplist 原来的长度
        // reqlen 是整个新节点的长度
        // nextdiff 是新节点的后继节点扩展 header 的长度(要么 0 字节,要么 4 个字节)
        // T = O(N)
        zl = ziplistResize(zl,curlen+reqlen+nextdiff);
        p = zl+offset;
    
        /* Apply memory move when necessary and update tail offset. */
        if (p[0] != ZIP_END)
        {
            // 新元素之后还有节点,因为新元素的加入,需要对这些原有节点进行调整
    
            /* Subtract one because of the ZIP_END bytes */
            // 移动现有元素,为新元素的插入空间腾出位置
            // T = O(N)
            memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);
    
            /* Encode this entry's raw length in the next entry. */
            // 将新节点的长度编码至后置节点
            // p+reqlen 定位到后置节点
            // reqlen 是新节点的长度
            // T = O(1)
            zipPrevEncodeLength(p+reqlen,reqlen);
    
            /* Update offset for tail */
            // 更新到达表尾的偏移量,将新节点的长度也算上
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);
    
            /* When the tail contains more than one entry, we need to take
             * "nextdiff" in account as well. Otherwise, a change in the
             * size of prevlen doesn't have an effect on the *tail* offset. */
            // 如果新节点的后面有多于一个节点
            // 那么程序需要将 nextdiff 记录的字节数也计算到表尾偏移量中
            // 这样才能让表尾偏移量正确对齐表尾节点
            // T = O(1)
            tail = zipEntry(p+reqlen);
            if (p[reqlen+tail.headersize+tail.len] != ZIP_END)
            {
                ZIPLIST_TAIL_OFFSET(zl) =
                    intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
            }
        }
        else
        {
            /* This element will be the new tail. */
            // 新元素是新的表尾节点
            ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);
        }
    
        /* When nextdiff != 0, the raw length of the next entry has changed, so
         * we need to cascade the update throughout the ziplist */
        // 当 nextdiff != 0 时,新节点的后继节点的(header 部分)长度已经被改变,
        // 所以需要级联地更新后续的节点
        if (nextdiff != 0)
        {
            offset = p-zl;
            // T  = O(N^2)
            zl = __ziplistCascadeUpdate(zl,p+reqlen);
            p = zl+offset;
        }
    
        /* Write the entry */
        // 一切搞定,将前置节点的长度写入新节点的 header
        p += zipPrevEncodeLength(p,prevlen);
        // 将节点值的长度写入新节点的 header
        p += zipEncodeLength(p,encoding,slen);
        // 写入节点值
        if (ZIP_IS_STR(encoding))
        {
            // T = O(N)
            memcpy(p,s,slen);
        }
        else
        {
            // T = O(1)
            zipSaveInteger(p,value,encoding);
        }
    
        // 更新列表的节点数量计数器
        // T = O(1)
        ZIPLIST_INCR_LENGTH(zl,1);
    
        return zl;
    }
    
    /*
     * 将长度为 slen 的字符串 s 推入到 zl 中。
     *
     * where 参数的值决定了推入的方向:
     * - 值为 ZIPLIST_HEAD 时,将新值推入到表头。
     * - 否则,将新值推入到表末端。
     *
     * 函数的返回值为添加新值后的 ziplist 。
     *
     * T = O(N^2)
     */
    unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where)
    {
    
        // 根据 where 参数的值,决定将值推入到表头还是表尾
        unsigned char *p;
        p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);
    
        // 返回添加新值后的 ziplist
        // T = O(N^2)
        return __ziplistInsert(zl,p,s,slen);
    }
    
    /* 
     * 根据给定索引,遍历列表,并返回索引指定节点的指针。
     *
     * 如果索引为正,那么从表头向表尾遍历。
     * 如果索引为负,那么从表尾向表头遍历。
     * 正数索引从 0 开始,负数索引从 -1 开始。
     *
     * 如果索引超过列表的节点数量,或者列表为空,那么返回 NULL 。
     *
     * T = O(N)
     */
    unsigned char *ziplistIndex(unsigned char *zl, int index)
    {
    
        unsigned char *p;
    
        zlentry entry;
    
        // 处理负数索引
        if (index < 0)
        {
    
            // 将索引转换为正数
            index = (-index)-1;
    
            // 定位到表尾节点
            p = ZIPLIST_ENTRY_TAIL(zl);
    
            // 如果列表不为空,那么。。。
            if (p[0] != ZIP_END)
            {
    
                // 从表尾向表头遍历
                entry = zipEntry(p);
                // T = O(N)
                while (entry.prevrawlen > 0 && index--)
                {
                    // 前移指针
                    p -= entry.prevrawlen;
                    // T = O(1)
                    entry = zipEntry(p);
                }
            }
    
            // 处理正数索引
        }
        else
        {
    
            // 定位到表头节点
            p = ZIPLIST_ENTRY_HEAD(zl);
    
            // T = O(N)
            while (p[0] != ZIP_END && index--)
            {
                // 后移指针
                // T = O(1)
                p += zipRawEntryLength(p);
            }
        }
    
        // 返回结果
        return (p[0] == ZIP_END || index > 0) ? NULL : p;
    }
    
    /* 
     * 返回 p 所指向节点的后置节点。
     *
     * 如果 p 为表末端,或者 p 已经是表尾节点,那么返回 NULL 。
     *
     * T = O(1)
     */
    unsigned char *ziplistNext(unsigned char *zl, unsigned char *p)
    {
        ((void) zl);
    
        // p 已经指向列表末端
        if (p[0] == ZIP_END)
        {
            return NULL;
        }
    
        // 指向后一节点
        p += zipRawEntryLength(p);
        if (p[0] == ZIP_END)
        {
            // p 已经是表尾节点,没有后置节点
            return NULL;
        }
    
        return p;
    }
    
    /*
     * 返回 p 所指向节点的前置节点。
     *
     * 如果 p 所指向为空列表,或者 p 已经指向表头节点,那么返回 NULL 。
     *
     * T = O(1)
     */
    unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p)
    {
        zlentry entry;
    
        // 如果 p 指向列表末端(列表为空,或者刚开始从表尾向表头迭代)
        // 那么尝试取出列表尾端节点
        if (p[0] == ZIP_END)
        {
            p = ZIPLIST_ENTRY_TAIL(zl);
            // 尾端节点也指向列表末端,那么列表为空
            return (p[0] == ZIP_END) ? NULL : p;
    
            // 如果 p 指向列表头,那么说明迭代已经完成
        }
        else if (p == ZIPLIST_ENTRY_HEAD(zl))
        {
            return NULL;
    
            // 既不是表头也不是表尾,从表尾向表头移动指针
        }
        else
        {
            // 计算前一个节点的节点数
            entry = zipEntry(p);
            assert(entry.prevrawlen > 0);
            // 移动指针,指向前一个节点
            return p-entry.prevrawlen;
        }
    }
    
    /*
     * 取出 p 所指向节点的值:
     *
     * - 如果节点保存的是字符串,那么将字符串值指针保存到 *sstr 中,字符串长度保存到 *slen
     *
     * - 如果节点保存的是整数,那么将整数保存到 *sval
     *
     * 程序可以通过检查 *sstr 是否为 NULL 来检查值是字符串还是整数。
     *
     * 提取值成功返回 1 ,
     * 如果 p 为空,或者 p 指向的是列表末端,那么返回 0 ,提取值失败。
     *
     * T = O(1)
     */
    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;
    
        // 取出 p 所指向的节点的各项信息,并保存到结构 entry 中
        // T = O(1)
        entry = zipEntry(p);
    
        // 节点的值为字符串,将字符串长度保存到 *slen ,字符串保存到 *sstr
        // T = O(1)
        if (ZIP_IS_STR(entry.encoding))
        {
            if (sstr)
            {
                *slen = entry.len;
                *sstr = p+entry.headersize;
            }
    
            // 节点的值为整数,解码值,并将值保存到 *sval
            // T = O(1)
        }
        else
        {
            if (sval)
            {
                *sval = zipLoadInteger(p+entry.headersize,entry.encoding);
            }
        }
    
        return 1;
    }
    
    /* Insert an entry at "p".
     *
     * 将包含给定值 s 的新节点插入到给定的位置 p 中。
     *
     * 如果 p 指向一个节点,那么新节点将放在原有节点的前面。
     *
     * T = O(N^2)
     */
    unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen)
    {
        return __ziplistInsert(zl,p,s,slen);
    }
    
    /*
     * 从 zl 中删除 *p 所指向的节点,
     * 并且原地更新 *p 所指向的位置,使得可以在迭代列表的过程中对节点进行删除。
     *
     * T = O(N^2)
     */
    unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p)
    {
    
        // 因为 __ziplistDelete 时会对 zl 进行内存重分配
        // 而内存充分配可能会改变 zl 的内存地址
        // 所以这里需要记录到达 *p 的偏移量
        // 这样在删除节点之后就可以通过偏移量来将 *p 还原到正确的位置
        size_t offset = *p-zl;
        zl = __ziplistDelete(zl,*p,1);
        
        *p = zl+offset;
    
        return zl;
    }
    
    /* 
     * 从 index 索引指定的节点开始,连续地从 zl 中删除 num 个节点。
     *
     * T = O(N^2)
     */
    unsigned char *ziplistDeleteRange(unsigned char *zl, unsigned int index, unsigned int num)
    {
    
        // 根据索引定位到节点
        // T = O(N)
        unsigned char *p = ziplistIndex(zl,index);
    
        // 连续删除 num 个节点
        // T = O(N^2)
        return (p == NULL) ? zl : __ziplistDelete(zl,p,num);
    }
    
    /*
     * 将 p 所指向的节点的值和 sstr 进行对比。
     *
     * 如果节点值和 sstr 的值相等,返回 1 ,不相等则返回 0 。
     *
     * T = O(N)
     */
    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))
        {
    
            // 节点值为字符串,进行字符串对比
    
            /* Raw compare */
            if (entry.len == slen)
            {
                // T = O(N)
                return memcmp(p+entry.headersize,sstr,slen) == 0;
            }
            else
            {
                return 0;
            }
        }
        else
        {
    
            // 节点值为整数,进行整数对比
    
            if (zipTryEncoding(sstr,slen,&sval,&sencoding))
            {
                // T = O(1)
                zval = zipLoadInteger(p+entry.headersize,entry.encoding);
                return zval == sval;
            }
        }
    
        return 0;
    }
    
    /* 
     * 寻找节点值和 vstr 相等的列表节点,并返回该节点的指针。
     *
     *
     * 每次比对之前都跳过 skip 个节点。
     *
     *
     * 如果找不到相应的节点,则返回 NULL 。
     *
     * T = O(N^2)
     */
    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;
    
        // 只要未到达列表末端,就一直迭代
        // T = O(N^2)
        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;
    
            if (skipcnt == 0)
            {
    
                /* Compare current entry with specified entry */
                // 对比字符串值
                // T = O(N)
                if (ZIP_IS_STR(encoding))
                {
                    if (len == vlen && memcmp(q, vstr, vlen) == 0)
                    {
                        return p;
                    }
                }
                else
                {
                    // 因为传入值有可能被编码了,
                    // 所以当第一次进行值对比时,程序会对传入值进行解码
                    // 这个解码操作只会进行一次
                    if (vencoding == 0)
                    {
                        if (!zipTryEncoding(vstr, vlen, &vll, &vencoding))
                        {
                            vencoding = UCHAR_MAX;
                        }
                        /* Must be non-zero by now */
                        assert(vencoding);
                    }
    
                    // 对比整数值
                    if (vencoding != UCHAR_MAX)
                    {
                        // T = O(1)
                        long long ll = zipLoadInteger(q, encoding);
                        if (ll == vll)
                        {
    
                            return p;
                        }
                    }
                }
    
                /* Reset skip count */
                skipcnt = skip;
            }
            else
            {
                /* Skip entry */
                skipcnt--;
            }
    
            /* Move to next entry */
            // 后移指针,指向后置节点
            p = q + len;
        }
    
        // 没有找到指定的节点
        return NULL;
    }
    
    /* Return length of ziplist.
     *
     * 返回 ziplist 中的节点个数
     *
     * T = O(N)
     */
    unsigned int ziplistLen(unsigned char *zl)
    {
    
        unsigned int len = 0;
    
        // 节点数小于 UINT16_MAX
        // T = O(1)
        if (intrev16ifbe(ZIPLIST_LENGTH(zl)) < UINT16_MAX)
        {
            len = intrev16ifbe(ZIPLIST_LENGTH(zl));
    
            // 节点数大于 UINT16_MAX 时,需要遍历整个列表才能计算出节点数
            // T = O(N)
        }
        else
        {
            unsigned char *p = zl+ZIPLIST_HEADER_SIZE;
            while (*p != ZIP_END)
            {
                p += zipRawEntryLength(p);
                len++;
            }
    
            /* Re-store length if small enough */
            if (len < UINT16_MAX) ZIPLIST_LENGTH(zl) = intrev16ifbe(len);
        }
    
        return len;
    }
    
    /* Return ziplist blob size in bytes.
     *
     * 返回整个 ziplist 占用的内存字节数
     *
     * T = O(1)
     */
    size_t ziplistBlobLen(unsigned char *zl)
    {
        return intrev32ifbe(ZIPLIST_BYTES(zl));
    }
    
    void ziplistRepr(unsigned char *zl)
    {
        unsigned char *p;
        int index = 0;
        zlentry entry;
    
        printf(
            "{total bytes %d} "
            "{length %u}
    "
            "{tail offset %u}
    ",
            intrev32ifbe(ZIPLIST_BYTES(zl)),
            intrev16ifbe(ZIPLIST_LENGTH(zl)),
            intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)));
        p = ZIPLIST_ENTRY_HEAD(zl);
        while(*p != ZIP_END)
        {
            entry = zipEntry(p);
            printf(
                "{"
                "addr 0x%08lx, "
                "index %2d, "
                "offset %5ld, "
                "rl: %5u, "
                "hs %2u, "
                "pl: %5u, "
                "pls: %2u, "
                "payload %5u"
                "} ",
                (long unsigned)p,
                index,
                (unsigned long) (p-zl),
                entry.headersize+entry.len,
                entry.headersize,
                entry.prevrawlen,
                entry.prevrawlensize,
                entry.len);
            p += entry.headersize;
            if (ZIP_IS_STR(entry.encoding))
            {
                if (entry.len > 40)
                {
                    if (fwrite(p,40,1,stdout) == 0) perror("fwrite");
                    printf("...");
                }
                else
                {
                    if (entry.len &&
                            fwrite(p,entry.len,1,stdout) == 0) perror("fwrite");
                }
            }
            else
            {
                printf("%lld", (long long) zipLoadInteger(p,entry.encoding));
            }
            printf("
    ");
            p += entry.len;
            index++;
        }
        printf("{end}
    
    ");
    }
  • 相关阅读:
    Linux开发初探
    Linux开发初探
    电脑使用说明书
    电脑使用说明书
    Delegates, Events, and Anonymous Methods 委托、事件与匿名方法
    CSS haslayout
    js与flash结合使用
    sgen.exe" exited with code 1.解决方法
    selenium
    selenium支付高版本的FireFox
  • 原文地址:https://www.cnblogs.com/yinbiao/p/11253950.html
Copyright © 2011-2022 走看看