深入解读Redis之数据类型解析-ZipList

wuchangjian2021-11-04 08:49:46编程学习

Redis源码之数据类型解析-ZipList

当前分析 Redis 版本为6.2,需要注意。

ZipList,压缩列表,可以任意包含多个节点。

基础结构

ZipList

压缩列表。其整体布局,<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>

  • uint32_t zlbytes,压缩列表占有的内存字节大小,包括 zlbytes 字段本身的四个字节。需要存储该值,以便能够调整整个结构的大小,而不是先遍历它。
  • uint32_t zltail,压缩列表的最后一个节点的偏移。允许在压缩列表远端弹出节点操作,而不需要整个遍历。
  • uint16_t zllen,节点的数量。当这个字段的值小于 UINT16_MAX(2^16-1) 时,该字段的值就是节点的数量。当这个值等于 UINT16_MAX 时,节点的真实数量就需要遍历整个列表计算。
  • uint8_t zlend,标记压缩列表尾端的特殊值 0xFF
压缩列表节点

在压缩列表中的节点都是以包含两部分信息的元数据开始的,然后才是 entry-data。第一个是前一个节点的长度。第二个是当前节点编码,整型或字符串。大致结构 <prevlen><encoding><entry-data>,不过有时编码(encoding)包含节点数据本身,也就是 <prevlen><encoding>这种简单结构。

  • prevlen,前一个节点的长度(字节)(该值长度可选值1|5)。如果前一个节点长度小于254字节,那么该属性的长度为1字节:前一个节点的长度就保存在这个字节里面。如果前一个节点长度大于等于254字节,那么该属性长度为5个字节,第一个字节会被设置为 254(0xFE),剩余的四个字节保存前一个节点的长度。
  • encoding,编码,依赖于节点的内容。当节点是字符串时,该属性第一个字节前两位会保存用于存储字符串长度的编码类型,剩余是字符串的实际长度。当节点是整型时,头两位均被设置为1,接下来的两位被用于表示在该头后存储的整型类型。第一个字节经常足够判断节点类型(整型|字符串)。
    • 字符串 00 编码一字节长,保存字符串长度6位,字符串最大长度为 2^6-1 字节。
    • 字符串 01 编码两字节长,保存字符串长度14位(大端),字符串最大长度为 2^14-1 字节。
    • 字符串 10 编码五字节长,保存字符串长度32位(大端),字符串最大长度为 2^32-1 字节。第一个字节的低6位并没有使用,0。
    • 整型 11000000int16_t 类型的整数(两字节)。
    • 整型 11010000int32_t 类型的整数(四个字节)。
    • 整型 11100000int64_t 类型的整数(八个字节)。
    • 整型 11110000,24位有符号整数。
    • 整型 11111110,8位有符号整数。
    • 整型 1111xxxxxxxx 介于 00011101 之间。从0到12的无符号整数。这个编码值实际是1到13,因为 00001111 不能使用,所以需要从这四位值中减1才是正确值。
    • 11111111,压缩列表特殊尾节点。
ziplistEntry

压缩节点值标准化模板。

// 在压缩列表中每个节点不是字符串就是整数
typedef struct {
    // 如果是字符串,长度就是slen
    unsigned char *sval;
    unsigned int slen;
    // 如果是整数,sval是NULL,lval保存整数
    long long lval;
} ziplistEntry;
zlentry

获取有关压缩列表节点信息的模板结构。这并不是节点实际编码,只是为了填充起来方便操作。

typedef struct zlentry {
    unsigned int prevrawlensize; // 用于编码前一个节点长度的字节,数?
    unsigned int prevrawlen; // 前一个节点的长度
    unsigned int lensize; // 用于编码该节点类型或长度的字节。比如,字符串有1|2|5字节的头,整型通常只有一个字节。
    unsigned int len; // 实际节点字节数,对于字符串就是字符串长度。对于整型就要取决于其范围      
    unsigned int headersize; // 头大小=prevrawlensize+lensize
    unsigned char encoding; // 节点编码方式
    unsigned char *p; // 节点的起始指针,也就是指向上一个节点长度属性。
} zlentry;

宏常量

ZIP_END

#define ZIP_END 255,压缩列表特殊的尾节点。

ZIP_BIG_PREVLEN

#define ZIP_BIG_PREVLEN 254,对于每个节点前仅代表一个字节的 prevlen 属性来说,ZIP_BIG_PREVLEN-1是其最大的字节数。否则,它就是形如 FE AA BB CC DD 的四个字节的无符号整数表示前一个节点的长度。

ZIP_STR_MASK

#define ZIP_STR_MASK 0xc0,字符串掩码(1100 0000)。

ZIP_INT_MASK

#define ZIP_INT_MASK 0x30,整型掩码(0011 0000)。

ZIP_STR_06B

#define ZIP_STR_06B (0 << 6),6位存储字符串长度编码的字符串(0000 0000)。

ZIP_STR_14B

#define ZIP_STR_14B (1 << 6),14位存储字符串长度编码的字符串(0100 0000)。

ZIP_STR_32B

#define ZIP_STR_32B (2 << 6),32位存储字符串长度编码的字符串(1000 0000)。

ZIP_INT_16B

#define ZIP_INT_16B (0xc0 | 0<<4),16位有符号整数(int16_t)(1100 0000)。

ZIP_INT_32B

#define ZIP_INT_32B (0xc0 | 1<<4),32位有符号整数(int32_t)(1101 0000)。

ZIP_INT_64B

#define ZIP_INT_64B (0xc0 | 2<<4),64位有符号整数(int64_t)(1110 0000)。

ZIP_INT_24B

#define ZIP_INT_24B (0xc0 | 3<<4),24位有符号整数(1111 0000)。

ZIP_INT_8B

#define ZIP_INT_8B 0xfe,8位有符号整数(1111 1110)。

ZIP_INT_IMM_MASK

#define ZIP_INT_IMM_MASK 0x0f,4位无符号整数掩码。

ZIP_INT_IMM_MIN

#define ZIP_INT_IMM_MIN 0xf1,4位无符号整数最小值 0。

ZIP_INT_IMM_MAX

#define ZIP_INT_IMM_MAX 0xfd,4位无符号整数最大值 12。

INT24_MAX

#define INT24_MAX 0x7fffff,24位有符号整数最大值。

INT24_MIN

#define INT24_MIN (-INT24_MAX - 1),24位有符号整数最小值。

ZIP_ENCODING_SIZE_INVALID

#define ZIP_ENCODING_SIZE_INVALID 0xff,编码大小无效值。

宏函数

ZIP_IS_STR

判断指定编码 enc 是否表示字符串。字符串节点不会以 11 作为首字节的最高有效位。

#define ZIP_IS_STR(enc) (((enc) & ZIP_STR_MASK) < ZIP_STR_MASK)
ZIPLIST_BYTES

返回压缩列表包含的总字节数指针。

#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))
// ziplist 结构
// zlbytes zltail zllen entry...entry zlend
ZIPLIST_TAIL_OFFSET

返回压缩列表最后一个节点的偏移指针

#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
// zl 偏移32位 zlbytes->zltail 
ZIPLIST_LENGTH

返回压缩列表节点数量指针,如果等于 UINT16_MAX,就需要遍历整个列表计算节点数量。

#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
ZIPLIST_HEADER_SIZE

压缩列表头大小:两个32位整型保存总字节数和最后一个节点偏移,16位整型为了节点数量。

#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))
ZIPLIST_END_SIZE

压缩列表结束节点大小。只有一个字节。

#define ZIPLIST_END_SIZE        (sizeof(uint8_t))
ZIPLIST_ENTRY_HEAD

返回压缩列表第一个节点指针。也就是 ziplist+headerSize

#define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)
ZIPLIST_ENTRY_TAIL

返回压缩列表中最后一个节点的指针。

#define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
ZIPLIST_ENTRY_END

返回压缩列表的最后一字节指针,也就是结束节点 FF

#define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)
ZIPLIST_INCR_LENGTH

增加压缩列表头中的长度 zllen

#define ZIPLIST_INCR_LENGTH(zl,incr) { \
    if (ZIPLIST_LENGTH(zl) < UINT16_MAX) \
        ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))+incr); \
}
ZIPLIST_ENTRY_ZERO

初始化压缩列表节点模板结构。

#define ZIPLIST_ENTRY_ZERO(zle) { \
    (zle)->prevrawlensize = (zle)->prevrawlen = 0; \
    (zle)->lensize = (zle)->len = (zle)->headersize = 0; \
    (zle)->encoding = 0; \
    (zle)->p = NULL; \
}
ZIP_ENTRY_ENCODING

ptr 指针字节中获取编码方式,并设置到 zlentry 结构中的 encoding 属性。

#define ZIP_ENTRY_ENCODING(ptr, encoding) do {  \
    (encoding) = ((ptr)[0]); \
    if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; \
} while(0)
ZIP_ASSERT_ENCODING

检测编码是否无效。

#define ZIP_ASSERT_ENCODING(encoding) do {                                     \
    assert(zipEncodingLenSize(encoding) != ZIP_ENCODING_SIZE_INVALID);         \
} while (0)
ZIP_DECODE_LENGTH

解码在 ptr 中编码的节点类型和数据长度(字符串长度,整型字节数)。lensize 对节点编码的字节数。len 节点长度。和 zipStoreEntryEncoding 部分类似。

#define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do {                    \
    if ((encoding) < ZIP_STR_MASK) {                                           \
        // 字符串编码长度 1|2|5
        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 {                                                               \
            // 异常编码
            (lensize) = 0;  \
            (len) = 0;    \
        }                                                                      \
    } else {                                                                   \
        // 整型节点 编码的长度都是1 数据字节数,需要根据encoding判定
        (lensize) = 1;                                                         \
        if ((encoding) == ZIP_INT_8B)  (len) = 1;                              \
        else if ((encoding) == ZIP_INT_16B) (len) = 2;                         \
        else if ((encoding) == ZIP_INT_24B) (len) = 3;                         \
        else if ((encoding) == ZIP_INT_32B) (len) = 4;                         \
        else if ((encoding) == ZIP_INT_64B) (len) = 8;                         \
        else if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX)   \
            (len) = 0; /* 4 bit immediate */                                   \
        else                                                                   \
            (lensize) = (len) = 0; // 异常编码                        \
    }                                                                          \
} while(0)
ZIP_DECODE_PREVLENSIZE

返回编码前一个节点长度使用的字节数。通过设置 prelensize

#define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do {                          \
    if ((ptr)[0] < ZIP_BIG_PREVLEN) {                                          \
        (prevlensize) = 1;                                                     \
    } else {                                                                   \
        (prevlensize) = 5;                                                     \
    }                                                                          \
} while(0)
ZIP_DECODE_PREVLEN

返回前一个节点的长度 prevlen,和编码这种长度的字节数 prevlensize

#define ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen) do {                     \
    ZIP_DECODE_PREVLENSIZE(ptr, prevlensize);                                  \
    if ((prevlensize) == 1) {                                                  \
        (prevlen) = (ptr)[0];                                                  \
    } else { /* prevlensize == 5 */                                            \
        (prevlen) = ((ptr)[4] << 24) |                                         \
                    ((ptr)[3] << 16) |                                         \
                    ((ptr)[2] <<  8) |                                         \
                    ((ptr)[1]);                                                \
    }                                                                          \
} while(0)

内部函数

zipEncodingLenSize

返回节点类型和长度编码的字节数,错误就返回 ZIP_ENCODING_SIZE_INVALID

static inline unsigned int zipEncodingLenSize(unsigned char encoding) {
    if (encoding == ZIP_INT_16B || encoding == ZIP_INT_32B ||
        encoding == ZIP_INT_24B || encoding == ZIP_INT_64B ||
        encoding == ZIP_INT_8B)
        return 1; // 整型就只有一个字节
    if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX) //特殊整型 4位
        return 1;
    if (encoding == ZIP_STR_06B)
        return 1;
    if (encoding == ZIP_STR_14B)
        return 2;
    if (encoding == ZIP_STR_32B)
        return 5;
    return ZIP_ENCODING_SIZE_INVALID;
}
zipIntSize

返回存储由 encoding 编码的整数所需的字节。

static inline 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;
    }
    if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX)
        return 0; /* 4位直接在encoding中 */
    redis_unreachable(); // abort
    return 0;
}
zipStoreEntryEncoding

将节点的编码头写入 p 中,如果 p 是空,直接返回编码这种长度所需的字节数。

unsigned int zipStoreEntryEncoding(unsigned char *p, unsigned char encoding, unsigned int rawlen) {
    unsigned char len = 1, buf[5]; // 最长为5,所以是buf[5]

    if (ZIP_IS_STR(encoding)) { // 字符串
    	// 由于给出的encoding可能不是为了字符串,所以还是需要rawlen进行判断
        if (rawlen <= 0x3f) { // 1  63 2^6-1 00
            if (!p) return len;
            buf[0] = ZIP_STR_06B | rawlen; // 类型和长度
            // 00 00 0000
            // 2bits 符号  6bits len
        } else if (rawlen <= 0x3fff) { // 2 10383 2^14-1 01
            len += 1;
            if (!p) return len;
            // ZIP_STR_14B 0100 0000 
            // 0x3f 0011 1111
            buf[0] = ZIP_STR_14B | ((rawlen >> 8) & 0x3f); // 两位+六位
            buf[1] = rawlen & 0xff; // 后八位
            // 0100 0000 0000 0000
            // 2bits 符号  14bits len
        } else { // 5 10
            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 { // 整型
        if (!p) return len;
        buf[0] = encoding;
    }
    memcpy(p,buf,len); // 存储
    return len;
}
zipStorePrevEntryLengthLarge

对前一个节点长度进行编码并写入 p。仅用于更大的编码(__ziplistCascadeUpdate)。

int zipStorePrevEntryLengthLarge(unsigned char *p, unsigned int len) {
    uint32_t u32;
    if (p != NULL) { // 分段?
        p[0] = ZIP_BIG_PREVLEN; // 254
        u32 = len;
        memcpy(p+1,&u32,sizeof(u32));
        memrev32ifbe(p+1);
    }
    return 1 + sizeof(uint32_t);
}
zipStorePrevEntryLength

对前一个节点长度进行编码并写入 p。如果 p 为空,返回需要编码这种长度的字节数。

unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) {
    if (p == NULL) {
        return (len < ZIP_BIG_PREVLEN) ? 1 : sizeof(uint32_t) + 1;
    } else {
        if (len < ZIP_BIG_PREVLEN) { // 大头区分
            p[0] = len;
            return 1;
        } else {
            return zipStorePrevEntryLengthLarge(p,len);
        }
    }
}
zipPrevLenByteDiff

返回编码前一个节点长度(prevlen)的长度差,当前一个节点大小发生变化时。

int zipPrevLenByteDiff(unsigned char *p, unsigned int len) {
    unsigned int prevlensize; // prevlen 的长度
    ZIP_DECODE_PREVLENSIZE(p, prevlensize); // 获取旧的prevlensize
    return zipStorePrevEntryLength(NULL, len) - prevlensize;
}
zipTryEncoding

检测字符串节点是否能转成整型。

int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding) { // **v 整型值 *encoding 对应的编码
    long long value;

    if (entrylen >= 32 || entrylen == 0) return 0;
    if (string2ll((char*)entry,entrylen,&value)) { // string2ll 字符串转换成长整型
        // 判断整型范围 确定其编码类型
        if (value >= 0 && value <= 12) {
            *encoding = ZIP_INT_IMM_MIN+value; // 编码 1111 xxxx
        } 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;
}
zipSaveInteger

保存整型 valuep

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) { // 24bits
        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) {
        // 值就在 encoding 中
    } else {
        assert(NULL);
    }
}
zipLoadInteger

p 中读取整型值。zipSaveInteger 的逆向操作。

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; // 需要-1
    } else {
        assert(NULL);
    }
    return ret;
}
zipEntry

使用节点 p 的信息填充结构体 e

static inline void zipEntry(unsigned char *p, zlentry *e) {
    ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen);
    ZIP_ENTRY_ENCODING(p + e->prevrawlensize, e->encoding);
    ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len);
    assert(e->lensize != 0); // 验证 encoding 有效
    e->headersize = e->prevrawlensize + e->lensize;
    e->p = p;
}
zipEntrySafe

zipEntry 的安全版本。主要针对非信任指针。它保证了不访问在 ziplist 范围外的内存。

static inline int zipEntrySafe(unsigned char* zl, size_t zlbytes, unsigned char *p, zlentry *e, int validate_prevlen) {
    unsigned char *zlfirst = zl + ZIPLIST_HEADER_SIZE; // 头节点指针
    unsigned char *zllast = zl + zlbytes - ZIPLIST_END_SIZE; // 尾节点指针
    // 判断指针是否移除宏定义函数
#define OUT_OF_RANGE(p) (unlikely((p) < zlfirst || (p) > zllast))

    // 如果没有头溢出压缩列表的可能,就走捷径(最大的 lensize 和 prevrawlensize 都是5字节)
    if (p >= zlfirst && p + 10 < zllast) {
        ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen);
        ZIP_ENTRY_ENCODING(p + e->prevrawlensize, e->encoding);
        ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len);
        e->headersize = e->prevrawlensize + e->lensize;
        e->p = p;
        if (unlikely(e->lensize == 0)) // 检测 e->lensize 是否为0
            return 0;
        if (OUT_OF_RANGE(p + e->headersize + e->len)) // 判断是否溢出 ziplist 范围
            return 0;
        if (validate_prevlen && OUT_OF_RANGE(p - e->prevrawlen)) // 判断prevlen是否溢出
            return 0;
        return 1;
    }

    if (OUT_OF_RANGE(p)) // 检测指针是否溢出
        return 0;

    ZIP_DECODE_PREVLENSIZE(p, e->prevrawlensize); // 设置 prevrawlensize
    if (OUT_OF_RANGE(p + e->prevrawlensize))
        return 0;

    // 检测编码是否有效
    ZIP_ENTRY_ENCODING(p + e->prevrawlensize, e->encoding);
    e->lensize = zipEncodingLenSize(e->encoding);
    if (unlikely(e->lensize == ZIP_ENCODING_SIZE_INVALID))
        return 0;

    // 检测节点头编码是否溢出
    if (OUT_OF_RANGE(p + e->prevrawlensize + e->lensize))
        return 0;

    // 解码prevlen和节点头长度
    ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen);
    ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len);
    e->headersize = e->prevrawlensize + e->lensize;

    // 检测节点是否溢出
    if (OUT_OF_RANGE(p + e->headersize + e->len))
        return 0;

    // 检测 prevlen 是否溢出
    if (validate_prevlen && OUT_OF_RANGE(p - e->prevrawlen))
        return 0;

    e->p = p;
    return 1;
#undef OUT_OF_RANGE // 取消宏定义 OUT_OF_RANGE 
}
zipRawEntryLengthSafe

计算 p 节点占用字节总数安全版。

static inline unsigned int zipRawEntryLengthSafe(unsigned char* zl, size_t zlbytes, unsigned char *p) {
    zlentry e;
    assert(zipEntrySafe(zl, zlbytes, p, &e, 0));
    return e.headersize + e.len;
}
zipRawEntryLength

计算 p 节点占用字节总数。zipRawEntryLengthSafe 的非安全版。

static inline unsigned int zipRawEntryLength(unsigned char *p) {
    zlentry e;
    zipEntry(p, &e);
    return e.headersize + e.len;
}
zipAssertValidEntry

验证节点是否溢出压缩列表范围。

static inline void zipAssertValidEntry(unsigned char* zl, size_t zlbytes, unsigned char *p) {
    zlentry e;
    assert(zipEntrySafe(zl, zlbytes, p, &e, 1));
}
ziplistResize

调整压缩列表大小。

unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
    zl = zrealloc(zl,len); // 重新分配空间
    ZIPLIST_BYTES(zl) = intrev32ifbe(len); // 设置总长度
    zl[len-1] = ZIP_END; // 重新设置结束节点
    return zl;
}
__ziplistCascadeUpdate

级联更新。当节点被插入时,我们需要去设置下一个节点的 prevlen 字段等于当前插入节点的长度。这会导致长度不能编码在1个字节内,下一个节点需要扩展去保存5字节编码的 prevlen。 这可以自由完成,因为这只发生在一个节点已经被插入的时候(可能导致 reallocmemmove)。然而,编码这个 prevlen 可能也需要对应节点扩展。这种影响可能会贯穿整个压缩列表,当有一连串长度接近 ZIP_BIG_PREVLEN 的节点,所以需要去检测 prevlen 能否被编码在每个连续节点内。prevlen 字段需要收缩的反转也会造成这种影响。

unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) { // 比较吃力
    // *p 指向不需要更新的第一个节点
    zlentry cur; // 模板节点
    size_t prevlen, prevlensize, prevoffset; // 上一个更新节点信息
    size_t firstentrylen; // 用于处理头插入
    size_t rawlen, curlen = intrev32ifbe(ZIPLIST_BYTES(zl));
    size_t extra = 0, cnt = 0, offset;
    size_t delta = 4; // 更新一个节点 prevlen 属性需要的额外字节数(5-1)
    unsigned char *tail = zl + intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)); // 尾节点指针

    // 空压缩列表
    if (p[0] == ZIP_END) return zl;
	
    zipEntry(p, &cur); // 不需要安全版,因为该输入指针在被返回它的函数已验证
    firstentrylen = prevlen = cur.headersize + cur.len; // 当前节点总长度=头长度+内容长度
    prevlensize = zipStorePrevEntryLength(NULL, prevlen); // 计算prevlen字段长度
    prevoffset = p - zl; // 距离zl偏移
    p += prevlen; // 往后偏移。这里的prevlen就是当前节点的总长度,所以,相加 切到下一个节点

    // 迭代压缩列表为了找出为了更新它需要的额外字节数
    while (p[0] != ZIP_END) { // 一直到结束节点
        assert(zipEntrySafe(zl, curlen, p, &cur, 0)); // 有效节点

        // 当 prevlen 没有更新时中止
        if (cur.prevrawlen == prevlen) break;

        // 当节点的 prevlensize 足够大时中止
        if (cur.prevrawlensize >= prevlensize) {
            if (cur.prevrawlensize == prevlensize) { // prevlen字段长度相等
                zipStorePrevEntryLength(p, prevlen); // 设置对应长度
            } else {
                // 这会导致收缩,我们需要避免,所以,将 prevlen 设置在可用的字节
                zipStorePrevEntryLengthLarge(p, prevlen);
            }
            break;
        }

        // cur.prevrawlen 之前的头节点(可能)。如果是头节点,自然也就是为0
        // 或者 前一个节点原始长度+额外长度==前一个节点总长度
        assert(cur.prevrawlen == 0 || cur.prevrawlen + delta == prevlen);

        // 更新前一个节点信息并增加指针
        rawlen = cur.headersize + cur.len; // 当前节点原始长度
        prevlen = rawlen + delta;  // 原始长度+额外长度=当前节点总长度
        prevlensize = zipStorePrevEntryLength(NULL, prevlen); // 重新计算存储长度字段大小
        prevoffset = p - zl; // 重新计算需要被更新节点距离 zl 偏移
        p += rawlen; // 往后偏移
        extra += delta; // 叠加补充长度
        cnt++; // 补充次数?
    }

    // 额外字节为0 所有更新已完成,或不需要更新
    if (extra == 0) return zl;

    // 更新尾节点偏移在循环之后
    if (tail == zl + prevoffset) { // 
        // 当最后一个需要更新的节点恰好是尾节点时,更新尾节点偏移除非这正是更新的唯一一个节点(这种情况下,尾节点偏移不会发生变化)
        if (extra - delta != 0) { // 补充字节大于额外字节,表示该尾节点不是唯一更新的节点,前面还有需要更新的节点
            // 减去尾节点补充的额外字节,就是前面节点补充字节总量
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra-delta);
        }
    } else {
        // 如果不是尾节点,就更好操作了,直接原尾节点偏移+补充字节
        ZIPLIST_TAIL_OFFSET(zl) =
            intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra);
    }

    // 现在 p 指针就在原始压缩列表第一个不需要改变字节处
    // 将之后的数据,移到新压缩列表
    offset = p - zl; // 计算偏移 前面节点就是需要变动的节点?
    zl = ziplistResize(zl, curlen + extra); // 调整zl大小=原总长度+补充长度
    p = zl + offset; // 直接移到不需要更新字节处
    // 总长度-偏移-1 需要拷贝的长度(不变动节点总长度)
    // p+extra 目标位置 不需要更新节点的新位置
    // p 拷贝位置
    memmove(p + extra, p, curlen - offset - 1);
    p += extra; // 移到不变动节点处 然后向前偏移 prevlen

    // 从尾到头迭代所有需要被更新的节点
    while (cnt) { // 补充次数
        zipEntry(zl + prevoffset, &cur); // 获取需要第一个被更新节点,反向,也就是之前循环最后一个需要被更新节点
        rawlen = cur.headersize + cur.len; // 当前节点原始长度
        // 将节点移向尾部 重置 prevlen
        // 撇开 当前节点保存上一个节点原始长度的长度(有变,向后对齐) 实际数据长度
        memmove(p - (rawlen - cur.prevrawlensize), 
                zl + prevoffset + cur.prevrawlensize, 
                rawlen - cur.prevrawlensize);
        p -= (rawlen + delta); // 将p移到原始长度+额外长度之前
        if (cur.prevrawlen == 0) { // 头节点,将其 prevlen 更新为第一个节点长度
            zipStorePrevEntryLength(p, firstentrylen);
        } else { // 否则就增加额外长度 4字节
            zipStorePrevEntryLength(p, cur.prevrawlen+delta);
        }
        // 前移到前一个节点。没问题。
        prevoffset -= cur.prevrawlen;
        cnt--;
    }
    return zl;
}
__ziplistDelete

从压缩列表中,删除从 p 开始的 num 个节点。返回压缩列表指针。

unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) {
    // i 循环变量
    // totlen 需要删除字节总数
    // deleted 需要删除的节点数量 deleted <= num
    unsigned int i, totlen, deleted = 0;
    size_t offset;
    int nextdiff = 0;
    zlentry first, tail; // 删除头节点
    size_t zlbytes = intrev32ifbe(ZIPLIST_BYTES(zl)); 

    zipEntry(p, &first); // 解析first
    for (i = 0; p[0] != ZIP_END && i < num; i++) { // 可能遇到ZIP_END提前中止 这也是deleted < num的原因
        p += zipRawEntryLengthSafe(zl, zlbytes, p); // p节点占用字节总数 += 直接向后偏移
        deleted++;
    }
	// p已经偏移到最后一个需要删除的节点位置
    assert(p >= first.p); // 自然偏移结果
    totlen = p-first.p; // 删除节点移除字节总数
    if (totlen > 0) { // 显然
        uint32_t set_tail;
        if (p[0] != ZIP_END) { // 未到结束节点
            // 与当前prevrawlen比较,存储当前节点的prevrawlen可能会增加或减少所需要的字节数
            // 这儿有个存储它的空间,因为它在正在删除节点时已经预先存储
            // first.prevrawlen-p.prevrawlen 0|4|-4
            nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);

            // 当p回跳时,总会有空间的
            // 如果新前一个节点很大,删除节点中就会有个5字节的prevlen头的节点,所以这儿肯定至少释放5个字节,我们只需要4字节
            p -= nextdiff; // 调整prevlensize 
            assert(p >= first.p && p<zl+zlbytes-1);
            zipStorePrevEntryLength(p,first.prevrawlen); // 存储 prevlen

            // 尾节点偏移前移totlen字节
            set_tail = intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))-totlen;

            // 当尾部包含多个节点时,还是需要考虑nextdiff。否则,prevlen大小变化对尾偏移没有影响
            assert(zipEntrySafe(zl, zlbytes, p, &tail, 1)); // 解析tail
            if (p[tail.headersize+tail.len] != ZIP_END) { // 补充 nextdiff
                set_tail = set_tail + nextdiff;
            }

            // 将尾部p移到压缩列表前面
            // 由于断言p>=first.p,我们知道totlen>=0,所以p>first.p并且也是保存了不能溢出,即使节点长度遭到破坏
            size_t bytes_to_move = zlbytes-(p-zl)-1; // -1 不包含结束节点?
            memmove(first.p,p,bytes_to_move);
        } else { // 已到结束节点。尾节点已被删除,不再需要释放空间
            set_tail = (first.p-zl)-first.prevrawlen; // 计算结束节点位置
        }

        // 调整压缩列表大小
        offset = first.p-zl;
        zlbytes -= totlen - nextdiff; // 减去总共移除的字节数和对prevlen大小的调整
        zl = ziplistResize(zl, zlbytes);
        p = zl+offset;

        ZIPLIST_INCR_LENGTH(zl,-deleted); // 更新节点数量

        // 设置上面计算好的尾偏移
        assert(set_tail <= zlbytes - ZIPLIST_END_SIZE);
        ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(set_tail);

        // 当nextdiff!=0,下一个节点的原始长度已经发生了变化,所以需要级联更新贯穿压缩列表
        if (nextdiff != 0)
            zl = __ziplistCascadeUpdate(zl,p);
    }
    return zl;
}
__ziplistInsert

在压缩列表 zl 指定节点 p 处插入新节点 s

unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
    // curlen 当前压缩列表字节总数
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, newlen;
    // prevlensize prevlen的大小 prevlen 前一个节点的大小
    unsigned int prevlensize, prevlen = 0;
    size_t offset;
    int nextdiff = 0;
    unsigned char encoding = 0;
    
    long long value = 123456789; // 初始化避免警告
    zlentry tail;

    // 找到已插入节点的prevlen和prevlensize
    if (p[0] != ZIP_END) { // 不在结束节点
        ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
    } else { // 在结束节点
        unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl); // 尾节点
        if (ptail[0] != ZIP_END) {
            prevlen = zipRawEntryLengthSafe(zl, curlen, ptail);
        }
    }

    // 检测需要插入的节点能否被编码
    if (zipTryEncoding(s,slen,&value,&encoding)) { // 尝试编码
        // 根据encoding设置合适的整型编码
        reqlen = zipIntSize(encoding);
    } else {
        // encoding无法使用。然而zipStoreEntryEncoding可以使用字符串长度表明编码方式
        reqlen = slen;
    }
    // 需要存储前一个节点长度和本身数据的空间
    reqlen += zipStorePrevEntryLength(NULL,prevlen); // 补充前一个节点长度prevlen
    reqlen += zipStoreEntryEncoding(NULL,encoding,slen); // 补充本身所需空间

    // 如果出入的位置不是尾部,就需要保证,下一个节点的prevlen可以存储该新节点的长度
    int forcelarge = 0; // 强制扩展
    // 结束节点就不管。
    // reqlen.lensize-p.prevlensize 1-5
    nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
    if (nextdiff == -4 && reqlen < 4) {
        nextdiff = 0;
        forcelarge = 1;
    }

    // 存储p偏移,因为realloc可能会改变zl地址
    offset = p-zl;
    newlen = curlen+reqlen+nextdiff; // 原长度+新节点长度+prevlen修正
    zl = ziplistResize(zl,newlen);
    p = zl+offset; // 重新获取p位置

    // 如果可以就应用内存移动并更新尾偏移
    if (p[0] != ZIP_END) {
        // -1 排除ZIP_END字节
        // reqlen newEntry len
        // 将p-nextdiff移到p+reqlen位置,长度为curlen-offset-1+nextdiff
        // p后就空出reqlen空间
        memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);

        // 对下一个节点的上一个节点(当前节点)原始长度编码
        if (forcelarge) // p+reqlen
            zipStorePrevEntryLengthLarge(p+reqlen,reqlen);
        else
            zipStorePrevEntryLength(p+reqlen,reqlen);

        // 更新尾偏移
        ZIPLIST_TAIL_OFFSET(zl) =
            intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);

        // 当尾部包含多个节点时,需要补充nextdiff。否则,prevlen大小的改变对尾偏移没有影响
        assert(zipEntrySafe(zl, newlen, p+reqlen, &tail, 1));
        if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
        }
    } else { // 该节点将成为新的尾
        ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);
    }

    // 当nextdiff非0时,下一个节点的原始长度会发生变化。所以需要级联更新贯穿整个压缩列表
    if (nextdiff != 0) {
        offset = p-zl;
        zl = __ziplistCascadeUpdate(zl,p+reqlen);
        p = zl+offset;
    }

    // 写入节点
    p += zipStorePrevEntryLength(p,prevlen); // 写入prevlen
    p += zipStoreEntryEncoding(p,encoding,slen); // 写入encoding
    // 写入entry-data
    if (ZIP_IS_STR(encoding)) { // 字符串拷贝
        memcpy(p,s,slen);
    } else {
        zipSaveInteger(p,value,encoding);
    }
    ZIPLIST_INCR_LENGTH(zl,1); // 更新节点数量
    return zl;
}
uintCompare

整型比较,快排。

int uintCompare(const void *a, const void *b) {
    return (*(unsigned int *) a - *(unsigned int *) b);
}
ziplistSaveValue

将一个从 vallval 读取的字符串快速存到目标结构体。

/* Helper method to store a string into from val or lval into dest */
static inline void ziplistSaveValue(unsigned char *val, unsigned int len, long long lval, ziplistEntry *dest) {
    dest->sval = val;
    dest->slen = len;
    dest->lval = lval;
}

接口函数

ziplistNew

创建一个新的压缩列表。返回 zl 对应指针。

unsigned char *ziplistNew(void) {
    unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE; // 计算 头+结束 长度 空列表
    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; // 返回 zl 指针
}
ziplistMerge

合并两个压缩列表,firstsecond,将 second 追加到 first。将大的压缩列表重新分配空间去包含新的合并列表。

unsigned char *ziplistMerge(unsigned char **first, unsigned char **second) {
    // 只要有一个列表为空,就中止,返回NULL
    if (first == NULL || *first == NULL || second == NULL || *second == NULL) return NULL;

    // 自然,相等也无法合并
    if (*first == *second) return NULL;

    size_t first_bytes = intrev32ifbe(ZIPLIST_BYTES(*first)); // zl1字节总量
    size_t first_len = intrev16ifbe(ZIPLIST_LENGTH(*first)); // zl1节点总量

    size_t second_bytes = intrev32ifbe(ZIPLIST_BYTES(*second)); // zl2字节总量
    size_t second_len = intrev16ifbe(ZIPLIST_LENGTH(*second)); // zl2节点总量

    int append;
    unsigned char *source, *target; // 源 & 目标
    size_t target_bytes, source_bytes;
    // 选择较大的列表作为目标列表,方便调整大小
    // 也必须知道,是追加还是前置到目标列表
    if (first_len >= second_len) { // 保留zl1,将zl2追加大zl1
        target = *first; // 目标
        target_bytes = first_bytes;
        source = *second; // 源
        source_bytes = second_bytes;
        append = 1;
    } else { // 保留zl2,将zl1前置到zl2
        target = *second;
        target_bytes = second_bytes;
        source = *first;
        source_bytes = first_bytes;
        append = 0;
    }

    // 计算最终所需字节总量(减去一对元数据 HEADER+END)
    size_t zlbytes = first_bytes + second_bytes -
                     ZIPLIST_HEADER_SIZE - ZIPLIST_END_SIZE;
    size_t zllength = first_len + second_len; // 节点总量,相加无碍

    // 联合zl节点总量应小于UINT16_MAX zllen类型限制
    zllength = zllength < UINT16_MAX ? zllength : UINT16_MAX;

    // 在开始分离内存之前保存尾偏移位置
    size_t first_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*first));
    size_t second_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*second));

    // 将目标扩展到新字节,然后追加或前置源
    target = zrealloc(target, zlbytes); // 重新分配空间 zlbytes
    if (append) { // 追加
        // 拷贝源 TARGET - END, SOURCE - HEADER
        memcpy(target + target_bytes - ZIPLIST_END_SIZE,
               source + ZIPLIST_HEADER_SIZE,
               source_bytes - ZIPLIST_HEADER_SIZE);
    } else { // 前置
        // 将目标内容移到 SOURCE-END,然后拷贝源到腾出的空间 SOURCE-END
        // SOURCE-END, TARGET-HEADER
        memmove(target + source_bytes - ZIPLIST_END_SIZE,
                target + ZIPLIST_HEADER_SIZE,
                target_bytes - ZIPLIST_HEADER_SIZE); // target整体后移
        memcpy(target, source, source_bytes - ZIPLIST_END_SIZE); // source前移
    }

    // 更新头部元信息 zlbytes zllen zltail
    ZIPLIST_BYTES(target) = intrev32ifbe(zlbytes);
    ZIPLIST_LENGTH(target) = intrev16ifbe(zllength);
    ZIPLIST_TAIL_OFFSET(target) = intrev32ifbe(
                                   (first_bytes - ZIPLIST_END_SIZE) +
                                   (second_offset - ZIPLIST_HEADER_SIZE));

    // 级联更新 主要针对 prevlen,这里从target+first_offset开始
    target = __ziplistCascadeUpdate(target, target+first_offset);

    // 源列表释放置空
    if (append) {
        zfree(*second);
        *second = NULL;
        *first = target;
    } else {
        zfree(*first);
        *first = NULL;
        *second = target;
    }
    return target;
}
ziplistPush

向压缩列表 zl 插入新节点 s,通过 where 指定从头还是尾,然后调用 __ziplistInsert 实现。

unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) {
    unsigned char *p;
    p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);
    return __ziplistInsert(zl,p,s,slen);
}
ziplistIndex

返回压缩列表迭代指定的偏移处的节点,负数从尾开始。

unsigned char *ziplistIndex(unsigned char *zl, int index) {
    unsigned char *p;
    unsigned int prevlensize, prevlen = 0;
    size_t zlbytes = intrev32ifbe(ZIPLIST_BYTES(zl));
    if (index < 0) { // 从尾开始
        index = (-index)-1;
        p = ZIPLIST_ENTRY_TAIL(zl); // 获取尾节点
        if (p[0] != ZIP_END) { // 非结束节点
            ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
            while (prevlen > 0 && index--) { // prevlen>0确保没到头节点
                p -= prevlen; // 向前偏移
                assert(p >= zl + ZIPLIST_HEADER_SIZE && p < zl + zlbytes - ZIPLIST_END_SIZE); // 断言 p 在正常范围内 未溢出
                ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
            }
        }
    } else { // 从头开始
        p = ZIPLIST_ENTRY_HEAD(zl); // 获取头节点
        while (index--) {
            p += zipRawEntryLengthSafe(zl, zlbytes, p);
            if (p[0] == ZIP_END) // 到结束节点
                break;
        }
    }
    if (p[0] == ZIP_END || index > 0) // 结束节点或idx超出zllen
        return NULL;
    zipAssertValidEntry(zl, zlbytes, p); // 验证节点合法
    return p; // 返回节点
}
ziplistNext

返回压缩列表指定节点 p 的下一个节点。

unsigned char *ziplistNext(unsigned char *zl, unsigned char *p) {
    ((void) zl);
    size_t zlbytes = intrev32ifbe(ZIPLIST_BYTES(zl));
	// 当前节点或下一个节点为结束节点,没有下一个节点
    if (p[0] == ZIP_END) return NULL;
    p += zipRawEntryLength(p);
    if (p[0] == ZIP_END) return NULL;
    zipAssertValidEntry(zl, zlbytes, p);
    return p;
}
ziplistPrev

返回压缩列表指定节点 p 的上一个节点。

unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p) {
    unsigned int prevlensize, prevlen = 0;
	// 结束节点返回NULL
    if (p[0] == ZIP_END) { // 如果指定节点是结束节点,就获取尾节点,判断是否为结束节点
        p = ZIPLIST_ENTRY_TAIL(zl);
        return (p[0] == ZIP_END) ? NULL : p;
    } else if (p == ZIPLIST_ENTRY_HEAD(zl)) { // 头节点返回NULL
        return NULL;
    } else {
        ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
        assert(prevlen > 0); // 非头节点
        p-=prevlen; // 往前偏移
        size_t zlbytes = intrev32ifbe(ZIPLIST_BYTES(zl));
        zipAssertValidEntry(zl, zlbytes, p);
        return p;
    }
}
ziplistGet

获取指针 p 处的节点,将相关信息保存在 *sstrsval (取决于节点的编码)。

// *sstr 字符串 slen 字符串长度
// 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; // 重置

    zipEntry(p, &entry);
    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);
        }
    }
    return 1;
}
ziplistInsert

在节点 p 后插入新节点。直接调用 __ziplistInsert 实现。

ziplistDelete

删除指定节点。

unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p) {
    size_t offset = *p-zl;
    zl = __ziplistDelete(zl,*p,1);
    *p = zl+offset;
    return zl;
}
ziplistDeleteRange

删除一系列节点。

unsigned char *ziplistDeleteRange(unsigned char *zl, int index, unsigned int num) {
    unsigned char *p = ziplistIndex(zl,index);
    return (p == NULL) ? zl : __ziplistDelete(zl,p,num);
}
ziplistReplace

替换 p 处的节点为 s,相当于删除然后插入。

unsigned char *ziplistReplace(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {

    // 获取当前节点的元信息
    zlentry entry;
    zipEntry(p, &entry);

    // 计算需要存储节点的长度,包括prevlen
    unsigned int reqlen;
    unsigned char encoding = 0;
    long long value = 123456789;
    if (zipTryEncoding(s,slen,&value,&encoding)) { // 尝试对s进行编码
        reqlen = zipIntSize(encoding); // 编码需要的字节数
    } else { // 字符串
        reqlen = slen; /* encoding == 0 */
    }
    reqlen += zipStoreEntryEncoding(NULL,encoding,slen); // encoding长度

    if (reqlen == entry.lensize + entry.len) { // 刚好合适
        // 简单重写节点
        p += entry.prevrawlensize;
        p += zipStoreEntryEncoding(p,encoding,slen); // encoding
        // 拷贝值
        if (ZIP_IS_STR(encoding)) {
            memcpy(p,s,slen);
        } else {
            zipSaveInteger(p,value,encoding);
        }
    } else { // 删除 & 新增
        zl = ziplistDelete(zl,&p);
        zl = ziplistInsert(zl,p,s,slen);
    }
    return zl;
}
ziplistFind

查找指向与指定节点相等的节点指针。

unsigned char *ziplistFind(unsigned char *zl, unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip) {
    int skipcnt = 0;
    unsigned char vencoding = 0;
    long long vll = 0;
    size_t zlbytes = ziplistBlobLen(zl);

    while (p[0] != ZIP_END) { // 不是结束节点
        struct zlentry e;
        unsigned char *q;

        assert(zipEntrySafe(zl, zlbytes, p, &e, 1));
        q = p + e.prevrawlensize + e.lensize;

        if (skipcnt == 0) {
            // 将当前节点和特殊节点比较
            if (ZIP_IS_STR(e.encoding)) { // 字符串
                if (e.len == vlen && memcmp(q, vstr, vlen) == 0) { // 找到就直接返回节点p
                    return p;
                }
            } else { // 整型
                if (vencoding == 0) {
                    if (!zipTryEncoding(vstr, vlen, &vll, &vencoding)) {
                        // 如果节点vstr无法被编码,就将vencoding设置为UCHAR_MAX。所以在下次就不用尝试
                        vencoding = UCHAR_MAX;
                    }
                    // 现在vencoding一定非0
                    assert(vencoding);
                }

                // 在vencoding不为UCHAR_MAX时(没有这种编码的可能性,不是有效的整型),比较当前节点和特殊节点
                if (vencoding != UCHAR_MAX) {
                    long long ll = zipLoadInteger(q, e.encoding);
                    if (ll == vll) {
                        return p;
                    }
                }
            }

            // 重置 skip 统计
            skipcnt = skip;
        } else { // 跳过节点
            skipcnt--;
        }
        p = q + e.len; // 节点偏移
    }

    return NULL;
}
ziplistLen

返回压缩列表的节点数量。

unsigned int ziplistLen(unsigned char *zl) {
    unsigned int len = 0;
    if (intrev16ifbe(ZIPLIST_LENGTH(zl)) < UINT16_MAX) { // 如果在UINT16_MAX内
        len = intrev16ifbe(ZIPLIST_LENGTH(zl));
    } else { // 循环计数
        unsigned char *p = zl+ZIPLIST_HEADER_SIZE;
        size_t zlbytes = intrev32ifbe(ZIPLIST_BYTES(zl));
        while (*p != ZIP_END) {
            p += zipRawEntryLengthSafe(zl, zlbytes, p);
            len++;
        }

        // 如果len小于UINT16_MAX,更新压缩列表的zllen
        if (len < UINT16_MAX) ZIPLIST_LENGTH(zl) = intrev16ifbe(len);
    }
    return len;
}
ziplistBlobLen

返回压缩列表字节总量。

size_t ziplistBlobLen(unsigned char *zl) {
    return intrev32ifbe(ZIPLIST_BYTES(zl));
}
ziplistRepr

标准打印压缩列表?

void ziplistRepr(unsigned char *zl) {
    unsigned char *p;
    int index = 0;
    zlentry entry;
    size_t zlbytes = ziplistBlobLen(zl);

    printf(
        "{total bytes %u} "
        "{num entries %u}\n"
        "{tail offset %u}\n",
        intrev32ifbe(ZIPLIST_BYTES(zl)),
        intrev16ifbe(ZIPLIST_LENGTH(zl)),
        intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)));
    p = ZIPLIST_ENTRY_HEAD(zl);
    while(*p != ZIP_END) {
        assert(zipEntrySafe(zl, zlbytes, p, &entry, 1));
        printf(
            "{\n"
                "\taddr 0x%08lx,\n"
                "\tindex %2d,\n"
                "\toffset %5lu,\n"
                "\thdr+entry len: %5u,\n"
                "\thdr len%2u,\n"
                "\tprevrawlen: %5u,\n"
                "\tprevrawlensize: %2u,\n"
                "\tpayload %5u\n",
            (long unsigned)p,
            index,
            (unsigned long) (p-zl),
            entry.headersize+entry.len,
            entry.headersize,
            entry.prevrawlen,
            entry.prevrawlensize,
            entry.len);
        printf("\tbytes: ");
        for (unsigned int i = 0; i < entry.headersize+entry.len; i++) {
            printf("%02x|",p[i]);
        }
        printf("\n");
        p += entry.headersize;
        if (ZIP_IS_STR(entry.encoding)) {
            printf("\t[str]");
            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("\t[int]%lld", (long long) zipLoadInteger(p,entry.encoding));
        }
        printf("\n}\n");
        p += entry.len;
        index++;
    }
    printf("{end}\n\n");
}
ziplistValidateIntegrity

验证数据结构的完整性。deep 是否深度验证,头和节点。

int ziplistValidateIntegrity(unsigned char *zl, size_t size, int deep,
    ziplistValidateEntryCB entry_cb, void *cb_userdata) {
    // 检测实际读取到的头大小 HEADER+END
    if (size < ZIPLIST_HEADER_SIZE + ZIPLIST_END_SIZE) return 0;

    // 检测在头部编码的大小是否匹配分配的大小
    size_t bytes = intrev32ifbe(ZIPLIST_BYTES(zl));
    if (bytes != size) return 0;

    // 检测最后一个字节必须是结束符
    if (zl[size - ZIPLIST_END_SIZE] != ZIP_END) return 0;

    // 检测尾偏移没有溢出分配空间
    if (intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)) > size - ZIPLIST_END_SIZE) return 0;

    // 非深度验证结束
    if (!deep) return 1;

    unsigned int count = 0;
    unsigned char *p = ZIPLIST_ENTRY_HEAD(zl); // 定位头节点
    unsigned char *prev = NULL;
    size_t prev_raw_size = 0;
    while(*p != ZIP_END) { // 没有结束字节
        struct zlentry e;
        // 解码节点头和尾
        if (!zipEntrySafe(zl, size, p, &e, 1)) return 0;

        // 确保说明前一个节点大小的准确性
        if (e.prevrawlen != prev_raw_size) return 0;

        // 选择性回调验证
        if (entry_cb && !entry_cb(p, cb_userdata)) return 0;

        // 节点偏移
        prev_raw_size = e.headersize + e.len;
        prev = p;
        p += e.headersize + e.len;
        count++;
    }

    // 确保zltail节点指向最后一个节点的开始位置
    if (prev != ZIPLIST_ENTRY_TAIL(zl)) return 0;

    // 检测在头部统计的节点数准确性
    unsigned int header_count = intrev16ifbe(ZIPLIST_LENGTH(zl));
    if (header_count != UINT16_MAX && count != header_count) return 0;

    return 1;
}
ziplistRandomPair

随机返回一对键和值,存储到 keyval 参数(val 可以为空在不需要的情况下)。

 // total_count 是提前计算好的 压缩列表节点数量的二分之一
void ziplistRandomPair(unsigned char *zl, unsigned long total_count, ziplistEntry *key, ziplistEntry *val) {
    int ret;
    unsigned char *p;

    assert(total_count); // 避免在损坏的压缩列表被0除

    // 生成偶数,因为压缩列表要保存 k-v 对
    int r = (rand() % total_count) * 2;
    p = ziplistIndex(zl, r);
    ret = ziplistGet(p, &key->sval, &key->slen, &key->lval);
    assert(ret != 0);

    if (!val) return;
    p = ziplistNext(zl, p);
    ret = ziplistGet(p, &val->sval, &val->slen, &val->lval);
    assert(ret != 0);
}
ziplistRandomPairs

随便返回多对(数量由 count 指定 )键值(存储到 keysvals 参数),可能会重复。

void ziplistRandomPairs(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals) {
    unsigned char *p, *key, *value;
    unsigned int klen = 0, vlen = 0;
    long long klval = 0, vlval = 0;

    // index属性必须在第一个,由于在uintCompare要使用
    typedef struct {
        unsigned int index;
        unsigned int order;
    } rand_pick; 
    rand_pick *picks = zmalloc(sizeof(rand_pick)*count);
    unsigned int total_size = ziplistLen(zl)/2;
    assert(total_size); // 非空

    // 创建一个随机索引池(有些可能会重复)
    for (unsigned int i = 0; i < count; i++) {
        picks[i].index = (rand() % total_size) * 2; // 生成偶数索引
        picks[i].order = i; // 保持选取它们的顺序
    }
    qsort(picks, count, sizeof(rand_pick), uintCompare); // 根据index进行快排

    // 从压缩列表中获取节点到一个输出数组(按照原始顺序)
    unsigned int zipindex = 0, pickindex = 0;
    p = ziplistIndex(zl, 0); // 头节点?为啥不用ZIPLIST_ENTRY_HEAD定位
    while (ziplistGet(p, &key, &klen, &klval) && pickindex < count) {
        p = ziplistNext(zl, p); // 下一个节点
        assert(ziplistGet(p, &value, &vlen, &vlval)); // 获取val
        while (pickindex < count && zipindex == picks[pickindex].index) {
            int storeorder = picks[pickindex].order;
            ziplistSaveValue(key, klen, klval, &keys[storeorder]); // 存储key
            if (vals)
                ziplistSaveValue(value, vlen, vlval, &vals[storeorder]); // 存储val
             pickindex++;
        }
        zipindex += 2;
        p = ziplistNext(zl, p); // 下一个节点
    }

    zfree(picks);
}
ziplistRandomPairsUnique

随机返回多对键值(唯一版)。

unsigned int ziplistRandomPairsUnique(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals) {
    unsigned char *p, *key;
    unsigned int klen = 0;
    long long klval = 0;
    unsigned int total_size = ziplistLen(zl)/2;
    unsigned int index = 0;
    if (count > total_size) count = total_size;

    /* To only iterate once, every time we try to pick a member, the probability
     * we pick it is the quotient of the count left we want to pick and the
     * count still we haven't visited in the dict, this way, we could make every
     * member be equally picked.*/
    p = ziplistIndex(zl, 0);
    unsigned int picked = 0, remaining = count;
    // 只需要循环一次,每次都会尝试去选取节点。选取它的概率是想要选取的剩余计数和在字典中尚未访问到的数量的商
    while (picked < count && p) {
        double randomDouble = ((double)rand()) / RAND_MAX;
        // 剩余计数/尚未访问到的计数
        double threshold = ((double)remaining) / (total_size - index);
        if (randomDouble <= threshold) {
            assert(ziplistGet(p, &key, &klen, &klval));
            ziplistSaveValue(key, klen, klval, &keys[picked]);
            p = ziplistNext(zl, p);
            assert(p);
            if (vals) {
                assert(ziplistGet(p, &key, &klen, &klval));
                ziplistSaveValue(key, klen, klval, &vals[picked]);
            }
            remaining--;
            picked++;
        } else {
            p = ziplistNext(zl, p);
            assert(p);
        }
        p = ziplistNext(zl, p);
        index++;
    }
    return picked;
}

本章小结

看完之后,大概知道,这是种什么数据了,一系列特殊编码的连续内存块组成的顺序型数据结构。确实很紧密,节约内存。每个内存块都有着不同的业务含义。当然,其中有些操作,还是比较迷惑,以后再说吧。

相关文章

Java并发体系-第二阶段-锁与同步-[1]

Java并发体系-第二阶段-锁与同步-[1]

并发编程中的三个问题 由于这个东西,和这篇文章比较配。所以虽然在第...

Python的异常与断言  appium断言

Python的异常与断言 appium断言

appium-unittest单元测试框架中,TestCase 类提供了一...

prettier 配置文件 解决自动添加逗号的问题(某马教程的来)

{   //配置vscode 的终端为 git,路径是自己的安装路径可以自...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。