redis之ziplist

本文深入探讨了Redis中ziplist的数据结构,包括其编码方式、内存优化、操作效率以及在不同场景下的优缺点。ziplist作为紧凑的内存存储结构,适用于存储小对象,如哈希和有序集合中的键值对。文章详细解释了ziplist的内部结构,如header、entry和zlend的组成,以及如何进行插入、删除和查找操作。同时,还展示了不同操作的代码实现,例如ziplistPush和ziplistDeleteRange。ziplist通过减少内存开销和提高访问速度,提升了Redis在小数据量情况下的性能表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

ziplist是一个特殊编码的内存使用低的双向链表。它可同时存储字符串和整数,并且数字类型存储的是实际的数值,而不是数字字符串。

  • 在O(1)的实际复杂度两端进行入队和出队操作
  • 但是每次操作都需要重新分配空间
  • 适合小对象紧凑存储,不适合大对象存储

结构

整体编码结构图

ziplist整体由三部分构成,header、entry、zlend。

  • header
    4字节的zlbytes,表示整个ziplist的大小
    4字节的zltail, 表示最后一个entry基于整个ziplist的偏移量
    2字节的zllen,表示entry的个数,当个数超过216-2时,zllen恒等于216-1,entry的个数需要遍历计算
  • zlen值恒等于255(0xFF)

请添加图片描述

entry编码结构图

entry由prevlen, encoding, entry-data三部分构成
请添加图片描述

  • prevlen
    prevlen表示前一个entry的总大小,当前一个entry的大小[0,253]时,prevlen只需要一个字节表示
    当大于等于254时,prevlen为5字节,第一个字节恒等于254(0xfe),后续4字节表示长度(小端序)
    请添加图片描述
  • encoding
    encoding根据后续的entry-data进行的编码,所以分为了字符串和整数
  • 字符串编码
    • |00xxxxxx| encoding 为1字节,其中6位最表示字符串长度 [0,63], 2^6-1
    • |01xxxxxx|xxxxxxxx|encoding 为2字节, 14位(大端序)表示字符串长度[0,16383], 2^14-1
    • |10000000|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|encoding为5字节, 4bytes(大端序)表示字符串长度[0,2^32-1]
      请添加图片描述
  • 整数编码(开头两位都是1)
    • |11000000|,数据为int16_t(2字节)
    • |11010000|,数据为int32_t(4字节)
    • |11100000|,数据为int64_t(8字节)
    • |11110000|, 数据为3字节, 24bit有符号数
    • |1111xxxx|, 数据范围在[1-13], 并且没有数据部分,就包含在编码中
      请添加图片描述

优点

  • 占用空间少,比如整数为实际值,不是数字字符串
  • 任意一个entry都可以向前或者向后遍历(类似双向链表)
  • 对比双向链表,ziplist是连续的内存,可随机访问,并且减少了next指针空间

缺点

  • 因为内存连续的,push/pop操作都需要重新分配空间
  • 因为内存连续的,修改数据可能需要重新分配空间
  • 插入或者修改数据将产生连锁更新问题
  • 查询某个entry需要遍历整个entry链

主要代码实现

编码掩码定义

#define ZIPLIST_HEAD 0 //从头开始操作
#define ZIPLIST_TAIL 1 //从尾开始操作

#define ZIP_END 255         /* Special "end of ziplist" entry. */
#define ZIP_BIG_PREVLEN 254 /* Max number of bytes of the previous entry, for
                               the "prevlen" field prefixing each entry, to be
                               represented with just a single byte. Otherwise
                               it is represented as FE AA BB CC DD, where
                               AA BB CC DD are a 4 bytes unsigned integer
                               representing the previous entry len. */

/* Different encoding/length possibilities */
#define ZIP_STR_MASK 0xc0  //11000000
#define ZIP_INT_MASK 0x30  //00110000
#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)

偏移宏函数定义

/* Macro to determine if the entry is a string. String entries never start
 * with "11" as most significant bits of the first byte. */
 //因为整数编码是11开头,所以小于0xc0的都是字符串
#define ZIP_IS_STR(enc) (((enc) & ZIP_STR_MASK) < ZIP_STR_MASK)

/* Utility macros.*/

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

/* 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)))

/* 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)

/* 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)))

/* 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))-1)

/* 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 (ZIPLIST_LENGTH(zl) < UINT16_MAX) \
        ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))+incr); \
}

操作ziplist的结构体

/* We use this function to receive information about a ziplist entry.
 * Note that this is not how the data is actually encoded, is just what we
 * get filled by a function in order to operate more easily. */
 //这个结构体只是在操作ziplist使用,实际内存结构还是前面图示的一样
typedef struct zlentry {
    unsigned int prevrawlensize; /* Bytes used to encode the previous entry len*/
    unsigned int prevrawlen;     /* Previous entry len. */
    unsigned int lensize;        /* Bytes used to encode this entry type/len.
                                    For example strings have a 1, 2 or 5 bytes
                                    header. Integers always use a single byte.*/
    unsigned int len;            /* Bytes used to represent the actual entry.
                                    For strings this is just the string length
                                    while for integers it is 1, 2, 3, 4, 8 or
                                    0 (for 4 bit immediate) depending on the
                                    number range. */
    unsigned int headersize;     /* prevrawlensize + lensize. */
    unsigned char encoding;      /* Set to ZIP_STR_* or ZIP_INT_* depending on
                                    the entry encoding. However for 4 bits
                                    immediate integers this can assume a range
                                    of values and must be range-checked. */
    unsigned char *p;            /* Pointer to the very start of the entry, that
                                    is, this points to prev-entry-len field. */
} zlentry;

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

创建一个空的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;
}

请添加图片描述

插入entry

  • 插入节点分为头插、尾插、和中间任意位置插入,其中头插和尾插只是一种特殊情况
    • 头插只是没有前面的entry
    • 尾插只是后面没有entry

例1. 插入一个长度为10的字符串“HelloWorld”

1. 首先定位到需要插入的位置,以push为例
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);
}

请添加图片描述

2. 计算新插入entry所需的总大小

需要计算prevlen, encoding, datalen

  • 计算prevlen
    因为p[0] == ZIP_END, 所以这个是插入尾部,计算最后一个entry大小unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
    而ptail[0] ==ZIP_END, 所以prevlen默认值为0
  • 计算数据长度
    • 尝试将数据转为数字,“HelloWorld”不是数字字符串,失败
    • 转换失败后,数据长度req_len = 10
    • 计算prevlen需要字节数, prevlen为0, 则需要1字节即可 req_len + 1 = 11
    • 计算encoding长度, 10字节在63字节范围内,所以只需6bit就可以表示,所以encoding只需要1字节,req_len+1=12
    • 如果不是尾插,计算当前entry的prevlen字节数和新插入entry时需要的prevlen编码字节数的插值, nextdiff=0
    • 总大小req_len = 1 + 1 + 10 + 0 = 12 字节
3. 记录偏移量

offset = p-zl;
后续重新分配空间后,p指针则会失效,所以需要通过offset重新计算获取p位置
请添加图片描述

4. 扩展ziplist的空间
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;
}

请添加图片描述

5. 重新偏移到插入位置

p = zl+offset;
请添加图片描述

6. 移动后续entry

因为是最后一个entry,所以不需要,只需要更新zltail
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);
请添加图片描述

7. 级联更新后续entry

因为nextdiff为0,所以不需要

8. 插入新的entry
  • 因为prevlen只需要1字节,并且前一个entry为空,所以prevlen的值是0
  • 字符串长度10,在[0,63]范围,所以encoding只需要1字节,后6位 001010表示长度
  • 后续10字节的“HelloWorld”
    请添加图片描述
9. 更新entry个数
 ZIPLIST_INCR_LENGTH(zl,1);

更新长度

例2. 头插插入长度360字节的字符串“I love redis …”

1. 定位到插入头部位置
p = ZIPLIST_ENTRY_HEAD(zl)

请添加图片描述

2. 计算新插入的entry需要的总大小
  • 根据当前entry计算前一个entry的大小,进而获取新插入的entry需要多少字节prevlen进行表示前一个entry的大小
    第一个字节p[0] < 254, 所以prevlensize只有一个字节,然后读取p[0]赋值给prevlen, prevlen = 0
    所以上一个entry长度为0,新插入的entry只需要1个字节进行表示, prevlensize = 1
  • 计算数据长度,尝试转换为数字,因为长度360长度超过32, 则直接失败, 数据长度直接等于数据长度,reqlen = slen; reqlen=360
int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding) {
    long long value;

    if (entrylen >= 32 || entrylen == 0) return 0;
    if (string2ll((char*)entry,entrylen,&value)) {
        ...
        *v = value;
        return 1;
    }
    return 0;
}
  • 编码encoding, 360长度的字符, 超过63,所以encoding需要2字节, 其中14bit 表示长度
  • 计算总大小 reqlen = prelensize + encoding + len = 1 + 2 + 360 = 363
  • 计算nextdiff
    • 当前entry的prevlensize为1字节,新插入的entry总大小为363, 超过了253,所以当前entry的prevlensize需要从1字节扩增至5字节
    • nextdiff = 4, 所以在扩容时需要多扩容4字节
int zipPrevLenByteDiff(unsigned char *p, unsigned int len) {
    unsigned int prevlensize;
    ZIP_DECODE_PREVLENSIZE(p, prevlensize);
    return zipStorePrevEntryLength(NULL, len) - prevlensize;
}
3. 扩容
  • 记录偏移量
  • 扩容
  • 重新定位
offset = p-zl;
zl = ziplistResize(zl,curlen+reqlen+nextdiff);
p = zl+offset;

请添加图片描述

4. 移动数据

因为插入位置不是最后一个位置,所以需要将插入位置后的数据往后移动,以腾出空间

   /* Subtract one because of the ZIP_END bytes */
   memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);

请添加图片描述

5. 写入插入entry后的prevlen

移动后,插入entry后,下一个entry的prevlen已经成垃圾数据了,需要重新更新(即使没有改变字节大小,也需要重新更新prevlen)

 zipStorePrevEntryLength(p+reqlen,reqlen);

请添加图片描述
新插入的节点总长367字节,超过253, 所以prevlen总共5字节,第一个字节254(0xFE), 后续4字节表示长度367(0x16F)

6. 更新zltail
 /* 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. */
zipEntry(p+reqlen, &tail);
if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {
    ZIPLIST_TAIL_OFFSET(zl) =
        intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
}

请添加图片描述
当前例子中后续只有一个entry,所以不需要增加nextdiff进行调整

7. 级联更新
if (nextdiff != 0) {
     offset = p-zl;
     zl = __ziplistCascadeUpdate(zl,p+reqlen);
     p = zl+offset;
 }
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;

    while (p[0] != ZIP_END) {
        zipEntry(p, &cur);
        rawlen = cur.headersize + cur.len;
        rawlensize = zipStorePrevEntryLength(NULL,rawlen);

        /* Abort if there is no next entry. */
        if (p[rawlen] == ZIP_END) break;
        zipEntry(p+rawlen, &next);

        /* 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". */
            offset = p-zl;
            extra = rawlensize-next.prevrawlensize;
            zl = ziplistResize(zl,curlen+extra);
            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. */
            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. */
            memmove(np+rawlensize,
                np+next.prevrawlensize,
                curlen-noffset-next.prevrawlensize-1);
            zipStorePrevEntryLength(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. */
                zipStorePrevEntryLengthLarge(p+rawlen,rawlen);
            } else {
                zipStorePrevEntryLength(p+rawlen,rawlen);
            }

            /* Stop here, as the raw length of "next" has not changed. */
            break;
        }
    }
    return zl;
}

本例子中刚好p+reqlen就是最后一个entry,所以进入更新逻辑后,if (p[rawlen] == ZIP_END) break;成立,更新结束

8.插入新entry
 /* 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);
    }

请添加图片描述
本例子中是字符串,所以使用memcpy

9. 更新entry个数
 ZIPLIST_INCR_LENGTH(zl,1);

请添加图片描述

例3. 在第一个entry后插入len=3 的“123”

1. 定位插入位置

请添加图片描述

2. 计算插入entry需要的总大小

prevlen: 前一个entry总长度0x16F, prevlen需要5字节编码
encoding: 可以转换为整数, 123, 在255范围内,所以只需要编码只需1字节,sizeof(encoding)=1 并且 reqlen=1

int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding) {
    long long value;

    if (entrylen >= 32 || entrylen == 0) return 0;
    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. */
        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;
}

datasize: 编码ZIP_INT_8B, 数据需要一个字节

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 */
    panic("Invalid integer encoding 0x%02X", encoding);
    return 0;
}

总大小 prevsize + encoding + datasize = 5 + 1 + 1 = 7
nextdiff:

 nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
 if (nextdiff == -4 && reqlen < 4) {
      nextdiff = 0;
      forcelarge = 1;
  }

这里nextdiff=-4, 但是本身数据长度转换后只有1,小于4,所以不需要重新缩小下一个节点的prevlensize,强制使用4字节表示小于254字节长度,最终nextdiff=0,forcelarge=1

3. 重新分配空间

realloc分配空间,并且设置zlend以及zlbytes
请添加图片描述

4. 移动数据

请添加图片描述

5. 更新下一个entry
 if (forcelarge)
    zipStorePrevEntryLengthLarge(p+reqlen,reqlen);
int zipStorePrevEntryLengthLarge(unsigned char *p, unsigned int len) {
    if (p != NULL) {
        p[0] = ZIP_BIG_PREVLEN;
        memcpy(p+1,&len,sizeof(len));
        memrev32ifbe(p+1);
    }
    return 1+sizeof(len);
}

本例中需要强制设置长度,不用收缩长度
请添加图片描述

6. 更新zltail

0x179 + 0x07 = 0x180
请添加图片描述

7. 级联更新

刚好本例子中下一个entry是最后一个entry,不需要后续的级联更新了

8. 写入entry

请添加图片描述

9. 更新entry个数

请添加图片描述

删除entry

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;

    zipEntry(p, &first);
    for (i = 0; p[0] != ZIP_END && i < num; i++) {
        p += zipRawEntryLength(p);
        deleted++;
    }

    totlen = p-first.p; /* Bytes taken by the element(s) to delete. */
    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. */
            nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);

            /* Note that there is always space when p jumps backward: if
             * the new previous entry is large, one of the deleted elements
             * had a 5 bytes prevlen header, so there is for sure at least
             * 5 bytes free and we need just 4. */
            p -= nextdiff;
            zipStorePrevEntryLength(p,first.prevrawlen);

            /* Update offset for tail */
            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. */
            zipEntry(p, &tail);
            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 */
            memmove(first.p,p,
                intrev32ifbe(ZIPLIST_BYTES(zl))-(p-zl)-1);
        } else {
            /* The entire tail was deleted. No need to move memory. */
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe((first.p-zl)-first.prevrawlen);
        }

        /* Resize and update length */
        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 */
        if (nextdiff != 0)
            zl = __ziplistCascadeUpdate(zl,p);
    }
    return zl;
}

例1. 从头开始删除2个entry

1. 定位到删除位置
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);
}
unsigned char *ziplistIndex(unsigned char *zl, int index) {
    unsigned char *p;
    unsigned int prevlensize, prevlen = 0;
    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--) {
                p -= prevlen;
                ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
            }
        }
    } else {
        p = ZIPLIST_ENTRY_HEAD(zl);
        while (p[0] != ZIP_END && index--) {
            p += zipRawEntryLength(p);
        }
    }
    return (p[0] == ZIP_END || index > 0) ? NULL : p;
}

请添加图片描述

2. 计算删除num个entry后的位置
zipEntry(p, &first);
 for (i = 0; p[0] != ZIP_END && i < num; i++) {
     p += zipRawEntryLength(p);
     deleted++;
 }
totlen = p-first.p; /* Bytes taken by the element(s) to delete. */

请添加图片描述

3. 计算nextdiff
nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);

本例中first是第一个entry, 所以first.prevrwalen=0,p的prevlen有5字节, 1 - 5 = -4

4. 跳过nextdiff
 p -= nextdiff;

请添加图片描述

5. 更新下一个entry的prevlen
 zipStorePrevEntryLength(p,first.prevrawlen);

请添加图片描述

6. 更新zltail
/* Update offset for tail */
ZIPLIST_TAIL_OFFSET(zl) =
   intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))-totlen);
zipEntry(p, &tail);
if (p[tail.headersize+tail.len] != ZIP_END) {
    ZIPLIST_TAIL_OFFSET(zl) =
       intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
}

请添加图片描述

7. 移动数据
memmove(first.p,p,
       intrev32ifbe(ZIPLIST_BYTES(zl))-(p-zl)-1);

请添加图片描述

8.重新分配空间
 /* Resize and update length */
offset = first.p-zl;
zl = ziplistResize(zl, intrev32ifbe(ZIPLIST_BYTES(zl))-totlen+nextdiff);

请添加图片描述

9. 更新entry长度
 ZIPLIST_INCR_LENGTH(zl,-deleted);

请添加图片描述

10. 级联更新后续entry
 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 */
if (nextdiff != 0)
    zl = __ziplistCascadeUpdate(zl,p);

刚好本例中是最后一个entry,所以这里不需要后续的更新

查找entry

  • 遍历整个ziplist
  • 根据当前entry编码(字符串,数值)进行比较
  • 如果找到则返回当前entry指针
  • 最终都没有找到,则返回NULL
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip) {
    int skipcnt = 0;
    unsigned char vencoding = 0;
    long long vll = 0;

    while (p[0] != ZIP_END) {
        unsigned int prevlensize, encoding, lensize, len;
        unsigned char *q;

        ZIP_DECODE_PREVLENSIZE(p, prevlensize);
        ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);
        q = p + prevlensize + lensize;

        if (skipcnt == 0) {
            /* Compare current entry with specified entry */
            if (ZIP_IS_STR(encoding)) {
                if (len == vlen && memcmp(q, vstr, vlen) == 0) {
                    return p;
                }
            } else {
                /* Find out if the searched field can be encoded. Note that
                 * we do it only the first time, once done vencoding is set
                 * to non-zero and vll is set to the integer value. */
                if (vencoding == 0) {
                    if (!zipTryEncoding(vstr, vlen, &vll, &vencoding)) {
                        /* If the entry can't be encoded we set it to
                         * UCHAR_MAX so that we don't retry again the next
                         * time. */
                        vencoding = UCHAR_MAX;
                    }
                    /* Must be non-zero by now */
                    assert(vencoding);
                }

                /* Compare current entry with specified entry, do it only
                 * if vencoding != UCHAR_MAX because if there is no encoding
                 * possible for the field it can't be a valid integer. */
                if (vencoding != UCHAR_MAX) {
                    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;
}

使用场景

  • hash数据很稀释时,可以将key-value连续存在ziplist中,存储小,查询也很快
  • 有序集合数据量小时, 小对象紧凑存储

hash小对象使用ziplist存储

$ ./redis-cli
127.0.0.1:6379> hset student name zhangsan
(integer) 1
127.0.0.1:6379> hset student age 18
(integer) 1
127.0.0.1:6379> object encoding student
"ziplist"
127.0.0.1:6379>

有序集合小对象使用ziplist存储

$ ./redis-cli
127.0.0.1:6379> zadd hello 1 a
(integer) 1
127.0.0.1:6379> zadd hello 2 bb
(integer) 1
127.0.0.1:6379> object encoding hello
"ziplist"
127.0.0.1:6379>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值