Redis ziplist

簡(jiǎn)介

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.

數(shù)據(jù)結(jié)構(gòu)

總體上ziplist由三部分組成ZIPLIST_HEADER 大小為(sizeof(uint32_t)*2+sizeof(uint16_t))仇矾。中間存儲(chǔ)entry贾陷,最后面存儲(chǔ)zip_end結(jié)束標(biāo)記

  • ZIPLIST OVERALL LAYOUT:
  • The general layout of the ziplist is as follows:
  • <zlbytes><zltail><zllen><entry><entry><zlend>
  1. zlbytes 一個(gè)uint32_t的數(shù)據(jù)婿屹,用來保存總大小
  2. zltail 用來保存最后一個(gè)entry的偏移
    3.zllen 一個(gè)uint16_t用來保存entry的數(shù)量勾怒。所以我們ziplist頭的大小是兩個(gè)uint32_t和一個(gè)uint16_t
    4.entry編碼的存儲(chǔ)數(shù)據(jù)
    5.zlend uint8_t保存結(jié)束標(biāo)記 ZIP_END
    因?yàn)楹蛕ipmap一樣都是線性保存蒋纬。所以沒有抽象出具體的數(shù)據(jù)結(jié)構(gòu)

對(duì)于ziplist的初始化

#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl))) /*第一個(gè)uint32_t是保存的zlbytes*/
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t)))) /*第二個(gè)uint32_t保存的zltail*/
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))/*兩個(gè)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); //設(shè)置
    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE); //當(dāng)前沒有數(shù)據(jù)偏移就是header_size 指向end代表空
    ZIPLIST_LENGTH(zl) = 0;//長(zhǎng)度為0
    zl[bytes-1] = ZIP_END;//末尾設(shè)置
    return zl;
}

了解了ziplist的線性結(jié)構(gòu),對(duì)于初始化來說很好理解

數(shù)據(jù)編碼

typedef struct zlentry {
    unsigned int prevrawlensize, prevrawlen;//上一個(gè)元素的數(shù)據(jù)的大小,保存上一個(gè)元素長(zhǎng)度的長(zhǎng)度
    unsigned int lensize, len;//保存當(dāng)前數(shù)據(jù)塊長(zhǎng)度的長(zhǎng)度,當(dāng)前數(shù)據(jù)塊的長(zhǎng)度
    unsigned int headersize;//prevrawlensize + lensize 就是保存前面數(shù)據(jù)信息和當(dāng)前數(shù)據(jù)信息的長(zhǎng)度
    unsigned char encoding;//編碼方式
    unsigned char *p;//開始位置
} zlentry;

ziplist有一個(gè)數(shù)據(jù)結(jié)構(gòu)zlentry 這個(gè)數(shù)據(jù)結(jié)構(gòu)并不是ziplist保存entry的數(shù)據(jù)結(jié)構(gòu)焰雕。這個(gè)數(shù)據(jù)結(jié)構(gòu)是通過對(duì)一個(gè)線性的entry進(jìn)行解碼出來的。他可以體現(xiàn)一個(gè)entry里面保存的數(shù)據(jù)芳杏。
prevrawlen:保存它上一個(gè)元素的數(shù)據(jù)大小矩屁。我們可以通過p-prevrawlen得到上一個(gè)entry的位置
len:保存當(dāng)前entry的總大小【粽裕可以確定自己的邊界
headersize:通過prevrawlensize+lensize來得到表示當(dāng)前header大小
encoding:數(shù)據(jù)的編碼方式吝秕,確定后面保存的數(shù)據(jù)該怎么獲取
p:保存自己的開始位置,根據(jù)p+headersize和得到我們數(shù)據(jù)開始的位置
通過這個(gè)數(shù)據(jù)結(jié)構(gòu)再加上我們前面zipmap的經(jīng)驗(yàn)空幻。我們基本可以猜測(cè)一下我們entry的線性結(jié)構(gòu)就是prevrawlensize+prevrawlen+lensize+len+encode烁峭,但是我們注意到我們的heaersize里面有沒有encoding的空間。所以我們encode的保存方式會(huì)放入len里面去秕铛,或者是根據(jù)encode來確定咱們len的長(zhǎng)度

編碼類型

既然我們上面注意到encode并沒有被單獨(dú)列出來约郁,所以我們來看下我們具體的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)

我們可以很直觀的發(fā)現(xiàn)我們的encode后面有個(gè)xxB耘柱,就是我們位的大小。其實(shí)我們就可以發(fā)現(xiàn)我們的encode包含了咱們的lensize的長(zhǎng)度棍现。所以更新下線性結(jié)構(gòu)為prevrawlensize+prevrawlen+encode+xxbit

我們會(huì)發(fā)現(xiàn)我們看到了6B 14B這些怪異的定義,一般來說我們的一個(gè)字節(jié)是8位镜遣,但是這里少了兩位己肮,那我們就來看下具體的實(shí)現(xiàn)。
其實(shí)我們細(xì)心就可以發(fā)現(xiàn)我們ZIP_STR_MASK 位1100 0000,其實(shí)意思就是我們的高兩位作為了我們的encode類型的存儲(chǔ)悲关。
ZIP_STR_06B 位0000 0000 ,他的encode的類型位置就是高兩位的00谎僻,這個(gè)時(shí)候我們還剩了6個(gè)字節(jié)沒有用,所以6B的來源就是這里寓辱,我們的低六位作為了存儲(chǔ)長(zhǎng)度艘绍。
同理ZIP_STR_14B 0100 0000 我們的類型標(biāo)致位為01 后面剩余了6個(gè)字節(jié),然后在加上一個(gè)額外分配的uint8_t空間組成了14B
ZIP_STR_32B 1000 0000 32B 代表四個(gè)uint8_t來組成秫筏。沒有再使用剩余的6字節(jié)诱鞠,這個(gè)時(shí)候范圍已經(jīng)足夠表示了。
我們?cè)诳磥韎nt的
ZIP_INT_MASK 0011 0000 它使用第三位和第四位來保存類型
ZIP_INT_16B 1100 0000 既00 代表著后面的數(shù)據(jù)2字節(jié)
ZIP_INT_32B 1101 0000 即01 代表后面數(shù)據(jù)4字節(jié)
ZIP_INT_64B 1110 0000 即10 代表后面數(shù)據(jù)8字節(jié)
ZIP_INT_24B 1111 0000 即11 代表后面數(shù)據(jù)3字節(jié)
ZIP_INT_8B 1111 1110 看不出代表為代表后面數(shù)據(jù)為1字節(jié)
這個(gè)8B看起來很突兀这敬,不符合前面的標(biāo)準(zhǔn)航夺,而且不符合中間兩位的原則。所以我們需要來看一下
我們發(fā)現(xiàn)下面的定義/* 4 bit integer immediate encoding */ 其實(shí)我們str+int的encode定義中排除zip_int_8B我們會(huì)發(fā)現(xiàn)崔涂,其實(shí)只用了前四位阳掐,如果不把后四位一起用起來,是不是有點(diǎn)不符合咱們Redis各種節(jié)約內(nèi)存的做法喃冷蚂。嘿嘿缭保,事實(shí)上,它還是真的用起來了蝙茶。
低四位可以保存的數(shù)據(jù)為0-15艺骂,所以有開發(fā)的空間,這時(shí)候我們的int還需要一個(gè)定義所以會(huì)占用1個(gè)表示的空間隆夯,我們的高四位的字節(jié)已經(jīng)用完彻亲,所以低四位不能全為0這樣會(huì)造成沖突,所以至少低位有一位需要不一樣吮廉,Redis給出的是最低位+1苞尝,所以占用了一個(gè)表示空間,全為1111即15的表示會(huì)造成和高四位構(gòu)成一個(gè)結(jié)束標(biāo)記宦芦,所以再排除宙址,所以我們還剩下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
給一個(gè)源碼的解釋:

  • 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.

現(xiàn)在我們對(duì)entry的表示結(jié)構(gòu)和編碼方式以及了解了调卑,后面的操作就是水到渠成了抡砂。直接來看Redis對(duì)于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]);   /*第一個(gè)字節(jié)保存的是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' */
/*獲取對(duì)encode是int的長(zhǎng)度*/
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);

插入

現(xiàn)在我們把編碼方面理清楚了之后大咱,直接來看插入操作。
首先我們的ziplist本質(zhì)上是一個(gè)雙向鏈表注益,保存了next信息和prev信息碴巾,其實(shí)next信息是利用內(nèi)存的連續(xù)性來保存的,不需要我們進(jìn)行額外的操作丑搔。prev信息是通過我們前面說的prevrawlen來存儲(chǔ)的這個(gè)字段的編碼方式和zipmap的存儲(chǔ)長(zhǎng)度方式一致就在這里不重復(fù)說了厦瓢。
插入本身除了把自己進(jìn)行編碼放入內(nèi)存之后,還有一個(gè)重要的操作就是維護(hù)上下文的鏈表關(guān)系啤月,由于內(nèi)存的連續(xù)煮仇,所以我們不需要維護(hù)next,但是我們需要維護(hù)prev.
我們的插入操作是在某個(gè)節(jié)點(diǎn)后插入谎仲,這時(shí)候我們是先要拿到一個(gè)節(jié)點(diǎn)這時(shí)候其實(shí)我們就有節(jié)點(diǎn)的信息(沒有的時(shí)候?qū)儆诳臻g的浙垫,也算是有節(jié)點(diǎn)信息吧),我們?cè)诓迦氲臅r(shí)候保存這個(gè)信息就足夠了郑诺。直接來看插入代碼

* 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) {//首先看這個(gè)插入的節(jié)點(diǎn)是不是空節(jié)點(diǎn)
        ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);//返回插入節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)的信息
    } else {
        unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);//找到尾節(jié)點(diǎn) (當(dāng)我們插入尾部的時(shí)候)
        if (ptail[0] != ZIP_END) {
            prevlen = zipRawEntryLength(ptail);//找到尾節(jié)點(diǎn)前一個(gè)節(jié)點(diǎn)的大小
        }
    }

    /* See if the entry can be encoded */
    if (zipTryEncoding(s,slen,&value,&encoding)) {//是否能夠轉(zhuǎn)換成int存儲(chǔ)
        /* '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;//如果不為空夹姥,我需要知道兩個(gè)length之間的差距(因?yàn)槲覀儾迦朐谇懊嫠栽瓉淼男枰膒revlen)
   //nextdiff只0 4 -4  //0代表兩個(gè)需要大小一樣 4 代表新的保存空間需要更大 -4 代表原來保存的位置太大了
    //reqlen
    if (nextdiff == -4 && reqlen < 4) {
        /*
         在一般的情況下不會(huì)觸發(fā)  nextdiff == -4  reqlen < 4 的情況 因?yàn)槿绻覀兊膔eqlen中prvelen 來源于p所以 prvlen=5 relen至少也是5
         但是喃 在updatecase里面當(dāng)節(jié)點(diǎn)變化的時(shí)候 當(dāng)前節(jié)點(diǎn)保存上一個(gè)節(jié)點(diǎn)的長(zhǎng)度的空間過大的時(shí)候 我們不會(huì)選擇縮小我們保存prvlen的空間 所以照成了這個(gè)情況
         繼續(xù)不縮小空間 還是用大的寫
         */
        
        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;//轉(zhuǎn)回到原來的偏移

    /* Apply memory move when necessary and update tail offset. */
    if (p[0] != ZIP_END) {
        /* Subtract one because of the ZIP_END bytes */
        //p+reqlen 如果新數(shù)據(jù)放入后的尾部
        //p+diff diff=0時(shí)很明顯
        //diff = -4  說明我們?cè)瓉肀4娴膌en太大了 我們的實(shí)際存儲(chǔ)肯定是第六位開始的(前面五個(gè)字節(jié)為len)原來前四個(gè)保存長(zhǎng)度的沒拷貝
        //diff = 4 說明我們?cè)瓉肀4娴膌en太小了 我們多拷貝了四個(gè)字節(jié)走 我們的實(shí)際數(shù)據(jù)拷貝么有問題
        //實(shí)際上就是留出后面保存prevlen的長(zhǎng)度
        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的時(shí)候把存放len的位置留出來了

        /* Update offset for tail */
        ZIPLIST_TAIL_OFFSET(zl) =
            intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);//更新新尾節(jié)點(diǎn)的位置 沒加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) {//找到下一個(gè)節(jié)點(diǎn) 我們這個(gè)節(jié)點(diǎn)后面沒有了的話 那就是代表被插入的節(jié)點(diǎn)就是尾節(jié)點(diǎn)了,diff的變化對(duì)他沒影響辙诞,否則的話就需要更新  可以加上 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);//設(shè)置 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 我們的節(jié)點(diǎn)會(huì)被改變 主要是存儲(chǔ)prvelen改變了 我們需要對(duì)后面的進(jìn)行循環(huán)改變 這部分代碼可以放入到 p[0] !ZIP_END里面去 最后再來匯總 因?yàn)?nextdiff產(chǎn)生的條件是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)) {//保存數(shù)據(jù)
        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) {//循環(huán)結(jié)束條件到達(dá)終點(diǎn)
        zipEntry(p, &cur);//獲取字節(jié)的情況
        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);//獲取字節(jié)后面大小的信息

        /* Abort when "prevlen" has not changed. */
        if (next.prevrawlen == rawlen) break;//大小沒變

        if (next.prevrawlensize < rawlensize) {//存儲(chǔ)空間不夠了
            /* 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;//下一個(gè)節(jié)點(diǎn)的位置
            noffset = np-zl;//下一個(gè)節(jié)點(diǎn)對(duì)于的偏移

            /* Update tail offset when next element is not the tail element. */
            if ((zl+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) != np) {//如果后面還有節(jié)點(diǎn) 就必須要更新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);//數(shù)據(jù)移動(dòng)
            zipPrevEncodeLength(np,rawlen);//編碼len

            /* Advance the cursor */
            p += rawlen;//到下一個(gè)位置
            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) {//循環(huán)結(jié)束條件到達(dá)終點(diǎn)
        zipEntry(p, &cur);//獲取字節(jié)的情況
        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);//獲取字節(jié)后面大小的信息

        /* Abort when "prevlen" has not changed. */
        if (next.prevrawlen == rawlen) break;//大小沒變

        if (next.prevrawlensize < rawlensize) {//存儲(chǔ)空間不夠了
            /* 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;//下一個(gè)節(jié)點(diǎn)的位置
            noffset = np-zl;//下一個(gè)節(jié)點(diǎn)對(duì)于的偏移

            /* Update tail offset when next element is not the tail element. */
            if ((zl+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) != np) {//如果后面還有節(jié)點(diǎn) 就必須要更新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);//數(shù)據(jù)移動(dòng)
            zipPrevEncodeLength(np,rawlen);//編碼len

            /* Advance the cursor */
            p += rawlen;//到下一個(gè)位置
            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的刪除操作特別簡(jiǎn)潔佃声,因?yàn)樗鞘褂眠吔鐏肀4鎛ext,所以直接把自身內(nèi)容刪除就好了.

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); //偏移到下一個(gè)節(jié)點(diǎn)
        deleted++;
    }

    totlen = p-first.p;
    if (totlen > 0) { //看下到底偏移沒
        if (p[0] != ZIP_END) {//后面還有節(jié)點(diǎn)
            /* 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);//后面的節(jié)點(diǎn)需要去保存刪除的第一個(gè)節(jié)點(diǎn)的prvlen
            p -= nextdiff; //多了的話就刪了 少了的話就哪要?jiǎng)h除的補(bǔ)上
            zipPrevEncodeLength(p,first.prevrawlen);//設(shè)置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) {//如果后面還有節(jié)點(diǎn)需要再加上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);//把后面的數(shù)據(jù)移上來
        } else {
            /* The entire tail was deleted. No need to move memory. */
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe((first.p-zl)-first.prevrawlen);//沒有節(jié)點(diǎn)就直接移動(dòng)數(shù)據(jù)
        }

        /* 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;
}

總結(jié)

ziplist 作為一個(gè)復(fù)雜的雙向list,他本身所需要的自我開銷特別的少倘要,就只有prevlen encode 其他保存的都是我們的數(shù)據(jù)信息圾亏。所以再節(jié)約內(nèi)存上會(huì)特別好。但是由于看不出具體的數(shù)據(jù)結(jié)構(gòu)封拧,會(huì)導(dǎo)致在操作上變得特別的復(fù)雜

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末志鹃,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子泽西,更是在濱河造成了極大的恐慌曹铃,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捧杉,死亡現(xiàn)場(chǎng)離奇詭異陕见,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)味抖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門评甜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人仔涩,你說我怎么就攤上這事忍坷。” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵佩研,是天一觀的道長(zhǎng)柑肴。 經(jīng)常有香客問我,道長(zhǎng)旬薯,這世上最難降的妖魔是什么晰骑? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮绊序,結(jié)果婚禮上硕舆,老公的妹妹穿的比我還像新娘。我一直安慰自己政模,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布蚂会。 她就那樣靜靜地躺著淋样,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胁住。 梳的紋絲不亂的頭發(fā)上趁猴,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音彪见,去河邊找鬼儡司。 笑死,一個(gè)胖子當(dāng)著我的面吹牛余指,可吹牛的內(nèi)容都是我干的捕犬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼酵镜,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼碉碉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起淮韭,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤垢粮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后靠粪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜡吧,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年占键,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昔善。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡畔乙,死狀恐怖耀鸦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤袖订,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布氮帐,位于F島的核電站,受9級(jí)特大地震影響洛姑,放射性物質(zhì)發(fā)生泄漏上沐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一楞艾、第九天 我趴在偏房一處隱蔽的房頂上張望参咙。 院中可真熱鬧,春花似錦硫眯、人聲如沸蕴侧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)净宵。三九已至,卻和暖如春裹纳,著一層夾襖步出監(jiān)牢的瞬間择葡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工剃氧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敏储,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓朋鞍,卻偏偏與公主長(zhǎng)得像已添,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子滥酥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 首先要了解設(shè)計(jì)思想T吞肌!恨狈!在 Redis 中疏哗,list 有兩種存儲(chǔ)方式:雙鏈表(LinkedList)和壓縮雙鏈表(...
    lmem閱讀 679評(píng)論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)禾怠,斷路器返奉,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 媽媽喊你回家過年——致“海”外游子 年前的工作糾結(jié)繁忙 人生的奮斗不會(huì)幾天就能收?qǐng)?總有一種默契在心頭縈繞 那是海...
    張光林閱讀 328評(píng)論 0 0
  • 老年吗氏, 是一段人生芽偏, 也是一個(gè)概念。 老年弦讽, 是一種存在污尉, 也是一場(chǎng)夢(mèng)幻膀哲。 當(dāng)別人問你“高壽”, 很可能是一個(gè)親密...
    曹煥甫閱讀 154評(píng)論 0 2
  • 媽媽人不高被碗,可腿出奇的長(zhǎng)某宪,相比之下腰身就短了,世上最難的事是陪她買衣服锐朴,走上一下午兴喂,也買不到合適的一件…我要結(jié)婚了...
    run_away閱讀 275評(píng)論 0 0