Redis源码阅读(五)ziplist

Ziplist

简介

Redis为了节约空间,在zset和hash的元素个数较少的时候使用ziplist进行存储。zip+list,是一系列的zip结构的数据链在一起。压缩链表是一块连续的内存空间,元素之间紧挨着存储,没有冗余空隙。

3.0之后list键已经不直接用ziplist和linkedlist作为底层实现了,取而代之的是quicklist
这些键的常规底层实现如下:
list键:双向链表
hash键:字典dict
zset键:跳跃表zskiplist

结构体

ziplist.h的结构体

压缩的元素,要么是string要么是整型

  • 如果是string,提供slen的方法
  • 如果是整型,sval字段为空,lval字段存储值。
/* Each entry in the ziplist is either a string or an integer. */
typedef struct {
    /* When string is used, it is provided with the length (slen). */
    unsigned char *sval;
    unsigned int slen;
    /* When integer is used, 'sval' is NULL, and lval holds the value. */
    long long lval;
} ziplistEntry;

ziplist.c中的结构体

##我们使用这个函数来接收关于ziplist条目的信息。
注意,这并不是数据的实际编码方式,只是为了更容易地操作,我们用函数填充数据。
typedef struct zlentry {
    unsigned int prevrawlensize; /* 用于编码前一个条目len的字节*/
    unsigned int prevrawlen;     /* 前一节点长度. */
    unsigned int lensize;        /* 用于编码此条目type/len的字节。例如,字符串有1、2或5个字节的头。整数总是使用单个字节。*/
    unsigned int len;            /* 用于表示实际条目长度的字节。对于字符串,这只是字符串长度
                                    
    unsigned int headersize;     /* prevrawlensize + lensize. */
    unsigned char encoding;      /* 根据条目编码设置为ZIP_STR_*或ZIP_INT_*
                                  * 然而,对于4位直接整数,这可以假设一个值的范围,并且必须进行范围检查。 */
    unsigned char *p;            /* 指向条目开始位置的指针,也就是说,它指向previ-entry-len字段。 */
    
} zlentry;

宏函数

将所有的值赋为0

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

从’ptr’指向的字节中提取编码,并将其设置为zlentry结构的’encoding’字段

/* Extract the encoding from the byte pointed by 'ptr' and set it into
 * 'encoding' field of the zlentry structure. */
#define ZIP_ENTRY_ENCODING(ptr, encoding) do {  \
    (encoding) = ((ptr)[0]); \
    if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; \
} while(0)

获取encoding字段,判断需要的字节数,来解析数据的类型和程序

#define ZIP_ENCODING_SIZE_INVALID 0xff
/* Return the number of bytes required to encode the entry type + length.
 * On error, return 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)
        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;
}

判断解析出来的长度字段是不是invalid 是的话则堵塞 一直判断

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

解析encoding字段 判断需要用来存储整型需要的空间大小

/* Return bytes needed to store integer encoded by '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 bit immediate */
    /* bad encoding, covered by a previous call to ZIP_ASSERT_ENCODING */
    redis_unreachable();
    return 0;
}

ziplist.c中的宏

宏定义

/* Different encoding/length possibilities */
#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 bit integer immediate encoding |1111xxxx| with xxxx between
 * 0001 and 1101. */
#define ZIP_INT_IMM_MASK 0x0f   /* Mask to extract the 4 bits value. To add
                                   one is needed to reconstruct the value. */
#define ZIP_INT_IMM_MIN 0xf1    /* 11110001 */
#define ZIP_INT_IMM_MAX 0xfd    /* 11111101 */

#define INT24_MAX 0x7fffff
#define INT24_MIN (-INT24_MAX - 1)

因为string类型从来都不“11”开头,所以只需要位运算获取前面两位并对比就可以知道是不是string

/* Macro to determine if the entry is a string. String entries never start
 * with "11" as most significant bits of the first byte. */
#define ZIP_IS_STR(enc) (((enc) & ZIP_STR_MASK) < ZIP_STR_MASK)

在这里插入图片描述

返回一个ziplist所组成的总字节数,相当于4字节的头存储的值

/* Utility macros.*/
/* Return total bytes a ziplist is composed of. */
#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))

返回ziplist的尾部的offset的大小,用于快速定位到最后一个节点,存储在一个四字节的数据中

/* Return the offset of the last item inside the ziplist. */
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))

两字节的数据大小,存储的内容是元素个数

/* Return the length of a ziplist, or UINT16_MAX if the length cannot be
 * determined without scanning the whole ziplist. */
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))

10字节存储ziplist的头部的大小

/* The size of a ziplist header: two 32 bit integers for the total
 * bytes count and last item offset. One 16 bit integer for the number
 * of items field. */
#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))

一字节存储结束标志

/* Size of the "end of ziplist" entry. Just one byte. */
#define ZIPLIST_END_SIZE        (sizeof(uint8_t))

可以获得第一个元素的结点的地址

/* Return the pointer to the first entry of a ziplist. */
#define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)

获取节点的最后一个节点
intrev32ifbe只有在目标主机是大端序的情况下,才执行实际转换的函数变体

/* Return the pointer to the last entry of a ziplist, using the
 * last entry offset inside the ziplist header. */
#define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))

返回指向ziplist的最后一个字节的指针,也就是ziplist FF条目的结束。

/* Return the pointer to the last byte of a ziplist, which is, the
 * end of ziplist FF entry. */
#define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-ZIPLIST_END_SIZE)

增加ziplist报头中的项字段的数量。注意这个宏不应该溢出无符号的16位整数,因为条目总是一次推入一个。当UINT16_MAX达到时,我们希望计数停留在那里,以表明需要一个完整的扫描来获得拉链内的项目的数量

/* Increment the number of items field in the ziplist header. Note that this
 * macro should never overflow the unsigned 16 bit integer, since entries are
 * always pushed one at a time. When UINT16_MAX is reached we want the count
 * to stay there to signal that a full scan is needed to get the number of
 * items inside the ziplist. */
#define ZIPLIST_INCR_LENGTH(zl,incr) { \
    if (intrev16ifbe(ZIPLIST_LENGTH(zl)) < UINT16_MAX) \
        ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))+incr); \
}

在任何情况下都不要让拉链增长超过1GB,不冒溢出风险

/* Don't let ziplists grow over 1GB in any case, don't wanna risk overflow in
 * zlbytes */
#define ZIPLIST_MAX_SAFETY_SIZE (1<<30)
int ziplistSafeToAdd(unsigned char* zl, size_t add) {
    size_t len = zl? ziplistBlobLen(zl): 0;
    if (len + add > ZIPLIST_MAX_SAFETY_SIZE)
        return 0;
    return 1;
}

ziplist.h中声明的函数

函数名备注
unsigned char *ziplistNew(void);
unsigned char *ziplistMerge(unsigned char **first, unsigned char **second);
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, int index, unsigned int num);
unsigned char *ziplistReplace(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen);
unsigned int ziplistCompare(unsigned char *p, unsigned char *s, unsigned int slen);
unsigned char *ziplistFind(unsigned char *zl, unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip);
unsigned int ziplistLen(unsigned char *zl);
size_t ziplistBlobLen(unsigned char *zl);
void ziplistRepr(unsigned char *zl);
typedef int (ziplistValidateEntryCB)(unsigned char p, unsigned int head_count, void* userdata);
int ziplistValidateIntegrity(unsigned char *zl, size_t size, int deep,iplistValidateEntryCB entry_cb, void *cb_userdata);
void ziplistRandomPair(unsigned char *zl, unsigned long total_count, ziplistEntry *key, ziplistEntry *val);
void ziplistRandomPairs(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals);
unsigned int ziplistRandomPairsUnique(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals);
int ziplistSafeToAdd(unsigned char* zl, size_t add);

implements

属性 prevrawlen、prevrawlensize
属性 prevrawlen 为前置节点的长度,而 prevrawlensize 是代表存储 prevrawlen 这个属性所需的字节大小

属性 len、lensize
属性 len 为当前节点值的长度,而 lensize 是代表存储 len 属性所需的字节大小。(注意:当节点保存的是字符串的时候,len 为字符串长度,如果保存的是整数值,len 为整数值的字节长度)

属性 headersize
属性 headersize 为当前节点 header 的长度,等于 prevrawlensize + lensize

属性 encoding
属性 encoding 为当前节点值所使用的编码类型

属性 p
属性 p 为一个指针,指向了当前节点的内存地址

在这里插入图片描述
previous_entry_length
属性 previous_entry_length 保存了前置节点的长度。

当前置节点的长度小于 254 字节的时候,那么 previous_entry_length 的长度将为1个字节,前置节点的长度就保存在这个字节里面。
当前置节点的长度大于等于 254 字节的时候,那么 previous_entry_length 的长度将为5个字节,这5个字节的第一个字节将被设置为254,然后剩下的四个字节保存前置节点的长度。
程序主要可以通过这个前置节点的长度,以及根据当前节点的起始地址来计算出前置节点的起始地址。压缩列表的从表头到表尾的遍历操作就是通过这样的原理来实现的。
encoding
属性 encoding 记录了对应节点的 content 属性所保存数据的类型以及长度。

encoding 的长度可能是一个字节、两个字节或者五个字节。它具体多少个字节,取决于 encoding 值的最高两位。当最高两位为00、01、10开头的时候,代表 content 存储的是字符串且 encoding 的长度分别为一个字节、两个字节或者五个字节。而为11开头的时候,即 content 存储的是整数且 encoding 的长度为一个字节。

00开头的时候:
encoding 占用了一个字节的空间,表示 content 能存储长度小于等于 2^6-1 字节的字符串

01开头的时候:
encoding 占用了两个字节的空间,表示 content 能存储长度小于等于 2^14-1 字节的字符串

10开头的时候:
encoding 占用了五个字节的空间,第一字节的后六位由0填充,剩下的表示 content 能存储长度小于等于 2^32-1 字节的字符串

11开头的时候:
encoding 占用了一个字节的空间,且由于编码的不同,content 存储的整数值范围都是不同的

encoding 值: 11000000 ,content 能存储 int16_t 类型的整数
encoding 值: 11010000 ,content 能存储 int32_t 类型的整数
encoding 值: 11100000 ,content 能存储 int64_t 类型的整数
encoding 值: 11110000 ,content 能存储 24 位有符号整数
encoding 值: 11111110 ,content 能存储 8 位有符号整数
encoding 值: 1111xxxx ,content 能存储 0-12 之间的值,没有明确的类型
content

压缩列表的连锁更新
假如在一个压缩列表中,有多个连续节点 e1 至 eN 且他们各自的长度都在250字节到253字节之间,加上上面提到的 previous_entry_length 的相关内容,这个属性记录每个前置节点的长度的时候,值将都是一个字节长度的。

在这里插入图片描述

此时我们把一个长度大于等于254字节的新节点 new 设置到压缩列表的表头的时候,那么 new 节点将成为 e1 节点的前置节点,而 e1 节点的 previous_entry_length 值只有1字节的长度,没有存储表示前置节点长度的5个字节长度,所以程序分配空间给 e1 多四个字节存储,接着 e2 也需要增加空间存储 e1 的长度,然后以此类推,引发了后面所有的节点需要更新空间,这就是连锁更新。
在这里插入图片描述
连锁更新会导致在最坏的情况下要对压缩列表 N 次空间重新分配,而每次空间重新分配的复杂度为 O(N),所以连锁更新的最坏时间复杂度为 O(N^2)

不过这种是需要恰好多个连续节点都是在 250-253字节之间,连锁更新才可能发生,这种情况概率很低。又假如是几个连续这样的 250-253 字节之间,对性能是不会造成影响的。

/* Create a new empty ziplist. */
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;
}
/* Insert item at "p". */
unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, newlen;
    unsigned int prevlensize, 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 tail;
		#获取prevlen
    /* Find out prevlen for the entry that is inserted. */
    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);
        }
    }
	##看能否解析
    /* See if the entry can be encoded */
    if (zipTryEncoding(s,slen,&value,&encoding)) {
        /* 'encoding' is set to the appropriate integer encoding */
        reqlen = zipIntSize(encoding);
    } else {
        /* 'encoding' is untouched, however zipStoreEntryEncoding 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. */
    reqlen += zipStorePrevEntryLength(NULL,prevlen);
    reqlen += zipStoreEntryEncoding(NULL,encoding,slen);
		##至此,reqlen 保存了存储当前节点数据占用字节数和 encoding 编码占用的字节数总和
    /* 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. */
     ##插入的地方不是尾部的时候,需要确保下一条目的前一条目域
    int forcelarge = 0;
     //当前节点占用的总字节减去存储前一个节点字段占用的字节
      //当前节点占用的总字节减去存储前一个节点字段占用的字节
    nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
    if (nextdiff == -4 && reqlen < 4) {
        nextdiff = 0;
        forcelarge = 1;
    }
	 ##保存offset 并空间重铸
    /* Store offset because a realloc may change the address of zl. */
     //当前节点占用的总字节减去存储前一个节点字段占用的字节
    offset = p-zl;
    newlen = curlen+reqlen+nextdiff;
    zl = ziplistResize(zl,newlen);
    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 */
        memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);
				//把当前节点占用的字节数存储到下一个节点的头部字段
        /* Encode this entry's raw length in the next entry. */
        if (forcelarge)
            zipStorePrevEntryLengthLarge(p+reqlen,reqlen);
        else
            zipStorePrevEntryLength(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. */
        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 {
        /* 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 */
     //是否触发连锁更新
    if (nextdiff != 0) {
        offset = p-zl;
        zl = __ziplistCascadeUpdate(zl,p+reqlen);
        p = zl+offset;
    }
 		//将节点写入指定位置
    /* Write the entry */
    p += zipStorePrevEntryLength(p,prevlen);
    p += zipStoreEntryEncoding(p,encoding,slen);
    if (ZIP_IS_STR(encoding)) {
        memcpy(p,s,slen);
    } else {
        zipSaveInteger(p,value,encoding);
    }
    ZIPLIST_INCR_LENGTH(zl,1);
    return zl;
}
/* Resize the ziplist. */
unsigned char *ziplistResize(unsigned char *zl, size_t len) {
    assert(len < UINT32_MAX);
    zl = zrealloc(zl,len);
    ZIPLIST_BYTES(zl) = intrev32ifbe(len);
    zl[len-1] = ZIP_END;
    return zl;
}

在’p’中写入条目的编码头。如果为NULL,则返回编码该长度所需的字节数。
参数:'encoding’是我们用于条目的编码。它可以是ZIP_INT_或ZIP_STR_,或者介于ZIP_INT_IMM MIN和zip_int_im_max之间对于单字节的小直接整数。
*'rawlen’仅用于ZIP_STR_*编码,是长度此条目表示的字符串。函数返回存储在’p’中的encoding/length *头所使用的字节数。

根据encoding类型和rawlen长度获取字段,然后存储到buf后,赋值给p,返回len的长度。

unsigned int zipStoreEntryEncoding(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. */
    memcpy(p,buf,len);
    return len;
}

解码编码在’ptr’中的条目编码类型和数据长度(字符串的字符串长度,整数条目的整数字节数)。'encoding’变量是输入,由调用者提取,'lensize’变量将保存编码条目长度所需的字节数,'len’变量将保存条目长度。在无效编码时,ennon lensize设置为0
zip解析长度的函数

/* Decode the entry encoding type and data length (string length for strings,
 * number of bytes used for the integer for integer entries) encoded in 'ptr'.
 * The 'encoding' variable is input, extracted by the caller, the 'lensize'
 * variable will hold the number of bytes required to encode the entry
 * length, and the 'len' variable will hold the entry length.
 * On invalid encoding error, lensize is set to 0. */
#define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do {                    \
    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) = ((uint32_t)(ptr)[1] << 24) |                               \
                    ((uint32_t)(ptr)[2] << 16) |                               \
                    ((uint32_t)(ptr)[3] <<  8) |                               \
                    ((uint32_t)(ptr)[4]);                                      \
        } else {                                                               \
            (lensize) = 0; /* bad encoding, should be covered by a previous */ \
            (len) = 0;     /* ZIP_ASSERT_ENCODING / zipEncodingLenSize, or  */ \
                           /* match the lensize after this macro with 0.    */ \
        }                                                                      \
    } else {                                                                   \
        (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; /* bad encoding */                          \
    }                                                                          \
} while(0)

对前面条目的长度进行解码,并将其写入“p”。只在较大的编码(required-in-ziplistCascadeUpdate)时使用

/* Encode the length of the previous entry and write it to "p". This only
 * uses the larger encoding (required in __ziplistCascadeUpdate). */
int zipStorePrevEntryLengthLarge(unsigned char *p, unsigned int len) {
    uint32_t u32;
    if (p != NULL) {
        p[0] = ZIP_BIG_PREVLEN;
        u32 = len;
        memcpy(p+1,&u32,sizeof(u32));
        memrev32ifbe(p+1);
    }
    return 1 + sizeof(uint32_t);
}

对前一项的长度进行编码,并将其写入“p”。如果"p"为NULL,则返回编码此长度所需的字节数,当p使用较大前缀长度,就使用上述的解析较大条目长度函数zipStorePrevEntryLengthLarge。

/* Encode the length of the previous entry and write it to "p". Return the
 * number of bytes needed to encode this length if "p" is NULL. */
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);
        }
    }
}

根据ptr[0]返回需要解析前一个条目的字节数

/* Return the number of bytes used to encode the length of the previous
 * entry. The length is returned by setting the var 'prevlensize'. */
#define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do {                          \
    if ((ptr)[0] < ZIP_BIG_PREVLEN) {                                          \
        (prevlensize) = 1;                                                     \
    } else {                                                                   \
        (prevlensize) = 5;                                                     \
    }                                                                          \
} while(0)

返回前一个元素的长度存储于prevlen,以及用于编码前一个元素长度的字节数存储在prevlensize。
'ptr’必须指向一个条目的前缀(编码前一个条目的长度,以便向后导航元素)。

/* Return the length of the previous element, and the number of bytes that
 * are used in order to encode the previous element length.
 * 'ptr' must point to the prevlen prefix of an entry (that encodes the
 * length of the previous entry in order to navigate the elements backward).
 * The length of the previous entry is stored in 'prevlen', the number of
 * bytes needed to encode the previous entry length are stored nin
 * '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)
/* Given a pointer 'p' to the prevlen info that prefixes an entry, this
 * function returns the difference in number of bytes needed to encode
 * the prevlen if the previous entry changes of size.
 *
 * So if A is the number of bytes used right now to encode the 'prevlen'
 * field.
 *
 * And B is the number of bytes that are needed in order to encode the
 * 'prevlen' if the previous element will be updated to one of size 'len'.
 *
 * Then the function returns B - A
 *
 * So the function returns a positive number if more space is needed,
 * a negative number if less space is needed, or zero if the same space
 * is needed. */
int zipPrevLenByteDiff(unsigned char *p, unsigned int len) {
    unsigned int prevlensize;
    ZIP_DECODE_PREVLENSIZE(p, prevlensize);
    return zipStorePrevEntryLength(NULL, len) - prevlensize;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值