Redis ziplist

简介

The ziplist is a specially encoded dually linked list that is designed to be very memory efficient. It stores both strings and integer values,where integers are encoded as actual integers instead of a series of characters. It allows push and pop operations on either side of the list in O(1) time. However, because every operation requires a reallocation of the memory used by the ziplist, the actual complexity is related to the amount of memory used by the ziplist.

数据结构

总体上ziplist由三部分组成ZIPLIST_HEADER 大小为(sizeof(uint32_t)*2+sizeof(uint16_t))。中间存储entry,最后面存储zip_end结束标记

  • ZIPLIST OVERALL LAYOUT:
  • The general layout of the ziplist is as follows:
  1. zlbytes 一个uint32_t的数据,用来保存总大小
  2. zltail 用来保存最后一个entry的偏移
    3.zllen 一个uint16_t用来保存entry的数量。所以我们ziplist头的大小是两个uint32_t和一个uint16_t
    4.entry编码的存储数据
    5.zlend uint8_t保存结束标记 ZIP_END
    因为和zipmap一样都是线性保存。所以没有抽象出具体的数据结构

对于ziplist的初始化

#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl))) /*第一个uint32_t是保存的zlbytes*/
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t)))) /*第二个uint32_t保存的zltail*/
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))/*两个uint32_t后面的uint_16_t保存的zllen*/
#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))

/* Create a new empty ziplist. */
unsigned char *ziplistNew(void) {
    unsigned int bytes = ZIPLIST_HEADER_SIZE+1; //heaer + end
    unsigned char *zl = zmalloc(bytes);
    ZIPLIST_BYTES(zl) = intrev32ifbe(bytes); //设置
    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE); //当前没有数据偏移就是header_size 指向end代表空
    ZIPLIST_LENGTH(zl) = 0;//长度为0
    zl[bytes-1] = ZIP_END;//末尾设置
    return zl;
}

了解了ziplist的线性结构,对于初始化来说很好理解

数据编码

typedef struct zlentry {
    unsigned int prevrawlensize, prevrawlen;//上一个元素的数据的大小,保存上一个元素长度的长度
    unsigned int lensize, len;//保存当前数据块长度的长度,当前数据块的长度
    unsigned int headersize;//prevrawlensize + lensize 就是保存前面数据信息和当前数据信息的长度
    unsigned char encoding;//编码方式
    unsigned char *p;//开始位置
} zlentry;

ziplist有一个数据结构zlentry 这个数据结构并不是ziplist保存entry的数据结构。这个数据结构是通过对一个线性的entry进行解码出来的。他可以体现一个entry里面保存的数据。
prevrawlen:保存它上一个元素的数据大小。我们可以通过p-prevrawlen得到上一个entry的位置
len:保存当前entry的总大小。可以确定自己的边界
headersize:通过prevrawlensize+lensize来得到表示当前header大小
encoding:数据的编码方式,确定后面保存的数据该怎么获取
p:保存自己的开始位置,根据p+headersize和得到我们数据开始的位置
通过这个数据结构再加上我们前面zipmap的经验。我们基本可以猜测一下我们entry的线性结构就是prevrawlensize+prevrawlen+lensize+len+encode,但是我们注意到我们的heaersize里面有没有encoding的空间。所以我们encode的保存方式会放入len里面去,或者是根据encode来确定咱们len的长度

编码类型

既然我们上面注意到encode并没有被单独列出来,所以我们来看下我们具体的encode类型

#define ZIP_STR_MASK 0xc0 /*1100 0000*/
#define ZIP_INT_MASK 0x30 /*0011 000*/
#define ZIP_STR_06B (0 << 6) /*0000 0000*/
#define ZIP_STR_14B (1 << 6) /*0100 0000*/
#define ZIP_STR_32B (2 << 6) /*1000 0000*/
#define ZIP_INT_16B (0xc0 | 0<<4)/*1100 0000*/
#define ZIP_INT_32B (0xc0 | 1<<4)/*1101 0000*/
#define ZIP_INT_64B (0xc0 | 2<<4)/*1110 0000*/
#define ZIP_INT_24B (0xc0 | 3<<4)/*1111 0000*/
#define ZIP_INT_8B 0xfe /*1111 1110*/
/* 4 bit integer immediate encoding */
#define ZIP_INT_IMM_MASK 0x0f /*0000 1111*/
#define ZIP_INT_IMM_MIN 0xf1    /* 1111 0001 */
#define ZIP_INT_IMM_MAX 0xfd    /* 1111 1101 */
#define ZIP_INT_IMM_VAL(v) (v & ZIP_INT_IMM_MASK)

我们可以很直观的发现我们的encode后面有个xxB,就是我们位的大小。其实我们就可以发现我们的encode包含了咱们的lensize的长度。所以更新下线性结构为prevrawlensize+prevrawlen+encode+xxbit

我们会发现我们看到了6B 14B这些怪异的定义,一般来说我们的一个字节是8位,但是这里少了两位,那我们就来看下具体的实现。
其实我们细心就可以发现我们ZIP_STR_MASK 位1100 0000,其实意思就是我们的高两位作为了我们的encode类型的存储。
ZIP_STR_06B 位0000 0000 ,他的encode的类型位置就是高两位的00,这个时候我们还剩了6个字节没有用,所以6B的来源就是这里,我们的低六位作为了存储长度。
同理ZIP_STR_14B 0100 0000 我们的类型标致位为01 后面剩余了6个字节,然后在加上一个额外分配的uint8_t空间组成了14B
ZIP_STR_32B 1000 0000 32B 代表四个uint8_t来组成。没有再使用剩余的6字节,这个时候范围已经足够表示了。
我们在看来int的
ZIP_INT_MASK 0011 0000 它使用第三位和第四位来保存类型
ZIP_INT_16B 1100 0000 既00 代表着后面的数据2字节
ZIP_INT_32B 1101 0000 即01 代表后面数据4字节
ZIP_INT_64B 1110 0000 即10 代表后面数据8字节
ZIP_INT_24B 1111 0000 即11 代表后面数据3字节
ZIP_INT_8B 1111 1110 看不出代表为代表后面数据为1字节
这个8B看起来很突兀,不符合前面的标准,而且不符合中间两位的原则。所以我们需要来看一下
我们发现下面的定义/* 4 bit integer immediate encoding */ 其实我们str+int的encode定义中排除zip_int_8B我们会发现,其实只用了前四位,如果不把后四位一起用起来,是不是有点不符合咱们Redis各种节约内存的做法喃。嘿嘿,事实上,它还是真的用起来了。
低四位可以保存的数据为0-15,所以有开发的空间,这时候我们的int还需要一个定义所以会占用1个表示的空间,我们的高四位的字节已经用完,所以低四位不能全为0这样会造成冲突,所以至少低位有一位需要不一样,Redis给出的是最低位+1,所以占用了一个表示空间,全为1111即15的表示会造成和高四位构成一个结束标记,所以再排除,所以我们还剩下0-12的空间给我们用,所以就有了:
ZIP_INT_IMM_MASK 0000 1111
ZIP_INT_IMM_MIN 0000 0001
ZIP_INT_IMM_MAX 0000 1101
ZIP_INT_8B 000 1110
给一个源码的解释:

  • The other header field of the entry itself depends on the contents of the
  • entry. When the entry is a string, the first 2 bits of this header will hold
  • the type of encoding used to store the length of the string, followed by the
  • actual length of the string. When the entry is an integer the first 2 bits
  • are both set to 1. The following 2 bits are used to specify what kind of
  • integer will be stored after this header. An overview of the different
  • types and encodings is as follows:
  • |00pppppp| - 1 byte
  •  String value with length less than or equal to 63 bytes (6 bits).
    
  • |01pppppp|qqqqqqqq| - 2 bytes
  •  String value with length less than or equal to 16383 bytes (14 bits).
    
  • |10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes
  •  String value with length greater than or equal to 16384 bytes.
    
  • |11000000| - 1 byte
  •  Integer encoded as int16_t (2 bytes).
    
  • |11010000| - 1 byte
  •  Integer encoded as int32_t (4 bytes).
    
  • |11100000| - 1 byte
  •  Integer encoded as int64_t (8 bytes).
    
  • |11110000| - 1 byte
  •  Integer encoded as 24 bit signed (3 bytes).
    
  • |11111110| - 1 byte
  •  Integer encoded as 8 bit signed (1 byte).
    
  • |1111xxxx| - (with xxxx between 0000 and 1101) immediate 4 bit integer.
  •  Unsigned integer from 0 to 12. The encoded value is actually from
    
  •  1 to 13 because 0000 and 1111 can not be used, so 1 should be
    
  •  subtracted from the encoded 4 bit value to obtain the right value.
    
  • |11111111| - End of ziplist.

现在我们对entry的表示结构和编码方式以及了解了,后面的操作就是水到渠成了。直接来看Redis对于encode 和 len的操作

/* Extract the encoding from the byte pointed by 'ptr' and set it into
 * 'encoding'. */
/*获取encode信息*/
#define ZIP_ENTRY_ENCODING(ptr, encoding) do {  \
    (encoding) = (ptr[0]);   /*第一个字节保存的是encode信息*/\
    if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK;/*str encode 使用的是高两位 只要高两位有0 肯定小于 ZIP_STR_MASK*/\
} while(0)

/* Return bytes needed to store integer encoded by 'encoding' */
/*获取对encode是int的长度*/
unsigned int zipIntSize(unsigned char encoding) {
    switch(encoding) {
    case ZIP_INT_8B:  return 1;
    case ZIP_INT_16B: return 2;
    case ZIP_INT_24B: return 3;
    case ZIP_INT_32B: return 4;
    case ZIP_INT_64B: return 8;
    default: return 0; /* 4 bit immediate */
    }
    assert(NULL);
    return 0;
}
#define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do {                    \
    ZIP_ENTRY_ENCODING((ptr), (encoding));/*获取encode的类型*/                   \
    if ((encoding) < ZIP_STR_MASK) { /*依次判断就好*/                             \
        if ((encoding) == ZIP_STR_06B) {                                       \
            (lensize) = 1;                                                     \
            (len) = (ptr)[0] & 0x3f;                                           \
        } else if ((encoding) == ZIP_STR_14B) {                                \
            (lensize) = 2;                                                     \
            (len) = (((ptr)[0] & 0x3f) << 8) | (ptr)[1];                       \
        } else if (encoding == ZIP_STR_32B) {                                  \
            (lensize) = 5;                                                     \
            (len) = ((ptr)[1] << 24) |                                         \
                    ((ptr)[2] << 16) |                                         \
                    ((ptr)[3] <<  8) |                                         \
                    ((ptr)[4]);                                                \
        } else {                                                               \
            assert(NULL);                                                      \
        }                                                                      \
    } else {                                                                   \
        (lensize) = 1;                                                         \
        (len) = zipIntSize(encoding);                                          \
    }                                                                          \
} while(0);

插入

现在我们把编码方面理清楚了之后,直接来看插入操作。
首先我们的ziplist本质上是一个双向链表,保存了next信息和prev信息,其实next信息是利用内存的连续性来保存的,不需要我们进行额外的操作。prev信息是通过我们前面说的prevrawlen来存储的这个字段的编码方式和zipmap的存储长度方式一致就在这里不重复说了。
插入本身除了把自己进行编码放入内存之后,还有一个重要的操作就是维护上下文的链表关系,由于内存的连续,所以我们不需要维护next,但是我们需要维护prev.
我们的插入操作是在某个节点后插入,这时候我们是先要拿到一个节点这时候其实我们就有节点的信息(没有的时候属于空间的,也算是有节点信息吧),我们在插入的时候保存这个信息就足够了。直接来看插入代码

* 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; //list的大小
    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;

    /* 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 = zipRawEntryLength(ptail);//找到尾节点前一个节点的大小
        }
    }

    /* See if the entry can be encoded */
    if (zipTryEncoding(s,slen,&value,&encoding)) {//是否能够转换成int存储
        /* 'encoding' is set to the appropriate integer encoding */
        reqlen = zipIntSize(encoding);
    } else {
        /* 'encoding' is untouched, however zipEncodeLength will use the
         * string length to figure out how to encode it. */
        reqlen = slen;
    }
    /* We need space for both the length of the previous entry and
     * the length of the payload. */
    reqlen += zipPrevEncodeLength(NULL,prevlen);//加上保存prevlen所需要的空间
    reqlen += zipEncodeLength(NULL,encoding,slen);//加上保存encode需要的空间

    /* 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;//如果不为空,我需要知道两个length之间的差距(因为我们插入在前面所以原来的需要更改prevlen)
   //nextdiff只0 4 -4  //0代表两个需要大小一样 4 代表新的保存空间需要更大 -4 代表原来保存的位置太大了
    //reqlen
    if (nextdiff == -4 && reqlen < 4) {
        /*
         在一般的情况下不会触发  nextdiff == -4  reqlen < 4 的情况 因为如果我们的reqlen中prvelen 来源于p所以 prvlen=5 relen至少也是5
         但是喃 在updatecase里面当节点变化的时候 当前节点保存上一个节点的长度的空间过大的时候 我们不会选择缩小我们保存prvlen的空间 所以照成了这个情况
         继续不缩小空间 还是用大的写
         */
        
        nextdiff = 0;
        forcelarge = 1;
    }

    /* Store offset because a realloc may change the address of zl. */
    offset = p-zl;//保存原来的偏移
    zl = ziplistResize(zl,curlen+reqlen+nextdiff);//总是增大
    p = zl+offset;//转回到原来的偏移

    /* Apply memory move when necessary and update tail offset. */
    if (p[0] != ZIP_END) {
        /* Subtract one because of the ZIP_END bytes */
        //p+reqlen 如果新数据放入后的尾部
        //p+diff diff=0时很明显
        //diff = -4  说明我们原来保存的len太大了 我们的实际存储肯定是第六位开始的(前面五个字节为len)原来前四个保存长度的没拷贝
        //diff = 4 说明我们原来保存的len太小了 我们多拷贝了四个字节走 我们的实际数据拷贝么有问题
        //实际上就是留出后面保存prevlen的长度
        memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);

        /* Encode this entry's raw length in the next entry. */
        if (forcelarge)
            zipPrevEncodeLengthForceLarge(p+reqlen,reqlen);
        else
            zipPrevEncodeLength(p+reqlen,reqlen);//我们的move的时候把存放len的位置留出来了

        /* Update offset for tail */
        ZIPLIST_TAIL_OFFSET(zl) =
            intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);//更新新尾节点的位置 没加diff的原因在下面

        /* 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) {//找到下一个节点 我们这个节点后面没有了的话 那就是代表被插入的节点就是尾节点了,diff的变化对他没影响,否则的话就需要更新  可以加上 if(nextdiff !=0 )再来做操作
            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);//设置 tail的偏移
    }

    /* 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) {//如果 不等于0 我们的节点会被改变 主要是存储prvelen改变了 我们需要对后面的进行循环改变 这部分代码可以放入到 p[0] !ZIP_END里面去 最后再来汇总 因为 nextdiff产生的条件是p[0] !=ZIP_END
        offset = p-zl;
        zl = __ziplistCascadeUpdate(zl,p+reqlen);//更新
        p = zl+offset;
    }

    /* Write the entry */
    p += zipPrevEncodeLength(p,prevlen);//保存prevlen的大小
    p += zipEncodeLength(p,encoding,slen);//保存自己的大小
    if (ZIP_IS_STR(encoding)) {//保存数据
        memcpy(p,s,slen);
    } else {
        zipSaveInteger(p,value,encoding);
    }
    ZIPLIST_INCR_LENGTH(zl,1);
    return zl;
}

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 = zipPrevEncodeLength(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;//diff
            zl = ziplistResize(zl,curlen+extra);//resize
            p = zl+offset;//还原p

            /* 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) {//如果后面还有节点 就必须要更新tail的位置信息
                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);//数据移动
            zipPrevEncodeLength(np,rawlen);//编码len

            /* Advance the cursor */
            p += rawlen;//到下一个位置
            curlen += extra;//len变成了
        } else {
            if (next.prevrawlensize > rawlensize) {//多了
                /* This would result in shrinking, which we want to avoid.
                 * So, set "rawlen" in the available bytes. */
                zipPrevEncodeLengthForceLarge(p+rawlen,rawlen);//懒些 用大的保存小的减少操作
            } else {
                zipPrevEncodeLength(p+rawlen,rawlen);//大小合适 保存下收工
            }

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

/* Delete "num" entries, starting at "p". Returns pointer to the ziplist. */
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;
    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);
            p -= nextdiff;
            zipPrevEncodeLength(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;
}
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 = zipPrevEncodeLength(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;//diff
            zl = ziplistResize(zl,curlen+extra);//resize
            p = zl+offset;//还原p

            /* 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) {//如果后面还有节点 就必须要更新tail的位置信息
                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);//数据移动
            zipPrevEncodeLength(np,rawlen);//编码len

            /* Advance the cursor */
            p += rawlen;//到下一个位置
            curlen += extra;//len变成了
        } else {
            if (next.prevrawlensize > rawlensize) {//多了
                /* This would result in shrinking, which we want to avoid.
                 * So, set "rawlen" in the available bytes. */
                zipPrevEncodeLengthForceLarge(p+rawlen,rawlen);//懒些 用大的保存小的减少操作
            } else {
                zipPrevEncodeLength(p+rawlen,rawlen);//大小合适 保存下收工
            }

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

删除

ziplist的删除操作特别简洁,因为他是使用边界来保存next,所以直接把自身内容删除就好了.

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++) {//万一你的num写多了喃
        p += zipRawEntryLength(p); //偏移到下一个节点
        deleted++;
    }

    totlen = p-first.p;
    if (totlen > 0) { //看下到底偏移没
        if (p[0] != ZIP_END) {//后面还有节点
            /* Storing `prevrawlen` in this entry may increase or decrease the
             * number of bytes required compare to the current `prevrawlen`.
             * There always is room to store this, because it was previously
             * stored by an entry that is now being deleted. */
            nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);//后面的节点需要去保存删除的第一个节点的prvlen
            p -= nextdiff; //多了的话就删了 少了的话就哪要删除的补上
            zipPrevEncodeLength(p,first.prevrawlen);//设置len

            /* Update offset for tail */
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))-totlen);//更新tail的位置

            /* 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) {//如果后面还有节点需要再加上nextdiff m
                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);//改变len
        p = zl+offset;//找到p

        /* 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)//和inset一样 如果有大小变化后面需要更新
            zl = __ziplistCascadeUpdate(zl,p);
    }
    return zl;
}

总结

ziplist 作为一个复杂的双向list,他本身所需要的自我开销特别的少,就只有prevlen encode 其他保存的都是我们的数据信息。所以再节约内存上会特别好。但是由于看不出具体的数据结构,会导致在操作上变得特别的复杂

你可能感兴趣的:(Redis ziplist)