Redis學習之ziplist壓縮列表源碼分析


一.壓縮列表ziplist在redis中的應用

1.做列表鍵

當一個列表鍵只包含少量列表項,並且每個列表項要么是小整數,要么是短字符串,那么redis會使用壓縮列表作為列表鍵的底層實現

2.哈希鍵

當一個哈希鍵只包含少量的鍵值對,並且每個鍵值對的鍵和值要么是小整數,要么是短字符串,那么redis會使用壓縮列表作為哈希鍵的底層實現

 

二.壓縮列表的定義:

壓縮列表ziplist是redis為了節約內存而開發的,是由一些了特殊編碼的連續內存塊組成的順序數據結構

 

三.關於壓縮列表的連鎖更新

更新的是什么?

更新的是每個結點的previous_entry_length以及結點的內存中的位置,涉及到內存空間的重分配

連鎖更新發生的情況有哪些?

比如在一個ziplist中,有多個連續的,長度介於250字節到253字節之間的結點e1到eN,因為e1到eN的所有結點都小於254字節,所以記錄這些結點的privious_entry_length屬性僅僅需要一個字節,這個時候如果我們將一個長度大於254字節的新結點插入到ziplist的頭部,那么e1到eN結點是所有privoius_entry_length屬性所占字節要從1字節變成5個字節,這里面涉及到對所有結點內存空間的重新分配,在這種最壞的情況下,需要對ziplist執行N次的空間重新分配操作,每次空間重分配操作的復雜度為O(N),所以連鎖更新最壞的情況下,復雜度為O(N*N)

盡管連鎖更新的復雜度高,但是真正造成性能問題的概率還是很低的,因為連鎖更新的極端情況並不少見,在結點數量少的時候,即使出現連鎖更新,也不會對性能造成影響,基於以上原因

ziplist的平均復雜度僅為O(N)

 

源碼分析:

ziplist.h

#define ZIPLIST_HEAD 0
#define ZIPLIST_TAIL 1

/*
前置說明:

ziplist定義成了宏屬性,在ziplist.c文件中
ziplist的結點zlentry的結構體也在ziplist.c文件中
看不懂為什么這么操作.....好迷啊

*/

//=========  API的定義  =====================//

//創建一個新的壓縮列表
unsigned char *ziplistNew(void);

//創建一個包含給定值的新結點,並將這個新結點加到壓縮列表的頭或尾
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, unsigned int index, unsigned int num);

//
unsigned int ziplistCompare(unsigned char *p, unsigned char *s, unsigned int slen);

//在壓縮列表中查找並返回包含了該值的結點
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip);

//返回壓縮列表目前包含的結點數量
unsigned int ziplistLen(unsigned char *zl);

//返回壓縮列表目前占用的內存字節數
size_t ziplistBlobLen(unsigned char *zl);

ziplist.c

/*
 * Ziplist 是為了盡可能地節約內存而設計的特殊編碼雙端鏈表。
 *
 * Ziplist 可以儲存字符串值和整數值,
 * 其中,整數值被保存為實際的整數,而不是字符數組。
 *
 * Ziplist 允許在列表的兩端進行 O(1) 復雜度的 push 和 pop 操作。
 * 但是,因為這些操作都需要對整個 ziplist 進行內存重分配,
 * 所以實際的復雜度和 ziplist 占用的內存大小有關。
 *-------------------------------------------------------------------------------------------------------------------------------------------------------------
 * Ziplist 的整體布局:
 *
 * 以下是 ziplist 的一般布局:
 *
 * <zlbytes><zltail><zllen><entry><entry><zlend>
 *
 * <zlbytes> 是一個無符號整數,保存着 ziplist 使用的內存數量。
 * 通過這個值,程序可以直接對 ziplist 的內存大小進行調整,
 * 而無須為了計算 ziplist 的內存大小而遍歷整個列表。
 *
 * <zltail> 保存着到達列表中最后一個節點的偏移量。
 * 這個偏移量使得對表尾的 pop 操作可以在無須遍歷整個列表的情況下進行。
 *
 * <zllen> 保存着列表中的節點數量。
 * 當 zllen 保存的值大於 2**16-2 時,
 * 程序需要遍歷整個列表才能知道列表實際包含了多少個節點。
 *
 * <zlend> 的長度為 1 字節,值為 255 ,標識列表的末尾。
 *
 * ZIPLIST ENTRIES:
 * ZIPLIST 節點:
 *
 * 每個 ziplist 節點的前面都帶有一個 header ,這個 header 包含兩部分信息:
 * 1)前置節點的長度,在程序從后向前遍歷時使用。
 * 2)當前節點所保存的值的類型和長度。
 *
 * 編碼前置節點的長度的方法如下:
 *
 * 1) 如果前置節點的長度小於 254 字節,那么程序將使用 1 個字節來保存這個長度值。
 *
 * 2) 如果前置節點的長度大於等於 254 字節,那么程序將使用 5 個字節來保存這個長度值:
 *    a) 第 1 個字節的值將被設為 254 ,用於標識這是一個 5 字節長的長度值。
 *    b) 之后的 4 個字節則用於保存前置節點的實際長度。
 *
 * header 另一部分的內容和節點所保存的值有關。
 *
 * 1) 如果節點保存的是字符串值,
 *    那么這部分 header 的頭 2 個位將保存編碼字符串長度所使用的類型,
 *    而之后跟着的內容則是字符串的實際長度。
 *
 * |00pppppp| - 1 byte
 *      String value with length less than or equal to 63 bytes (6 bits).
 *      字符串的長度小於或等於 63 字節。
 * |01pppppp|qqqqqqqq| - 2 bytes
 *      String value with length less than or equal to 16383 bytes (14 bits).
 *      字符串的長度小於或等於 16383 字節。
 * |10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes
 *      String value with length greater than or equal to 16384 bytes.
 *      字符串的長度大於或等於 16384 字節。
 *
 * 2) 如果節點保存的是整數值,
 *    那么這部分 header 的頭 2 位都將被設置為 1 ,
 *    而之后跟着的 2 位則用於標識節點所保存的整數的類型。
 *
 * |11000000| - 1 byte
 *      Integer encoded as int16_t (2 bytes).
 *      節點的值為 int16_t 類型的整數,長度為 2 字節。
 * |11010000| - 1 byte
 *      Integer encoded as int32_t (4 bytes).
 *      節點的值為 int32_t 類型的整數,長度為 4 字節。
 * |11100000| - 1 byte
 *      Integer encoded as int64_t (8 bytes).
 *      節點的值為 int64_t 類型的整數,長度為 8 字節。
 * |11110000| - 1 byte
 *      Integer encoded as 24 bit signed (3 bytes).
 *      節點的值為 24 位(3 字節)長的整數。
 * |11111110| - 1 byte
 *      Integer encoded as 8 bit signed (1 byte).
 *      節點的值為 8 位(1 字節)長的整數。
 * |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.
 *      節點的值為介於 0 至 12 之間的無符號整數。
 *      因為 0000 和 1111 都不能使用,所以位的實際值將是 1 至 13 。
 *      程序在取得這 4 個位的值之后,還需要減去 1 ,才能計算出正確的值。
 *      比如說,如果位的值為 0001 = 1 ,那么程序返回的值將是 1 - 1 = 0 。
 * |11111111| - End of ziplist.
 *      ziplist 的結尾標識
 *
 *
 * 所有整數都表示為小端字節序。
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>
#include "zmalloc.h"
#include "util.h"
#include "ziplist.h"
#include "endianconv.h"
#include "redisassert.h"

/*
 * ziplist 末端標識符,以及 5 字節長長度標識符
 */
#define ZIP_END 255
#define ZIP_BIGLEN 254

/*
 * 字符串編碼和整數編碼的掩碼
 */
#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 位整數編碼的掩碼和類型
 */
#define ZIP_INT_IMM_MASK 0x0f
#define ZIP_INT_IMM_MIN 0xf1    /* 11110001 */
#define ZIP_INT_IMM_MAX 0xfd    /* 11111101 */
#define ZIP_INT_IMM_VAL(v) (v & ZIP_INT_IMM_MASK)

/*
 * 24 位整數的最大值和最小值
 */
#define INT24_MAX 0x7fffff
#define INT24_MIN (-INT24_MAX - 1)

/*
 * 查看給定編碼 enc 是否字符串編碼
 */
#define ZIP_IS_STR(enc) (((enc) & ZIP_STR_MASK) < ZIP_STR_MASK)

/*
 * ziplist 屬性宏
 */
// 定位到 ziplist 的 bytes 屬性,該屬性記錄了整個 ziplist 所占用的內存字節數
// 用於取出 bytes 屬性的現有值,或者為 bytes 屬性賦予新值
#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))
// 定位到 ziplist 的 offset 屬性,該屬性記錄了到達表尾節點的偏移量
// 用於取出 offset 屬性的現有值,或者為 offset 屬性賦予新值
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
// 定位到 ziplist 的 length 屬性,該屬性記錄了 ziplist 包含的節點數量
// 用於取出 length 屬性的現有值,或者為 length 屬性賦予新值
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
// 返回 ziplist 表頭的大小
#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))
// 返回指向 ziplist 第一個節點(的起始位置)的指針
#define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)
// 返回指向 ziplist 最后一個節點(的起始位置)的指針
#define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
// 返回指向 ziplist 末端 ZIP_END (的起始位置)的指針
#define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)

/*
空白 ziplist 示例圖

area        |<---- ziplist header ---->|<-- end -->|

size          4 bytes   4 bytes 2 bytes  1 byte
            +---------+--------+-------+-----------+
component   | zlbytes | zltail | zllen | zlend     |
            |         |        |       |           |
value       |  1011   |  1010  |   0   | 1111 1111 |
            +---------+--------+-------+-----------+
                                       ^
                                       |
                               ZIPLIST_ENTRY_HEAD
                                       &
address                        ZIPLIST_ENTRY_TAIL
                                       &
                               ZIPLIST_ENTRY_END

非空 ziplist 示例圖

area        |<---- ziplist header ---->|<----------- entries ------------->|<-end->|

size          4 bytes  4 bytes  2 bytes    ?        ?        ?        ?     1 byte
            +---------+--------+-------+--------+--------+--------+--------+-------+
component   | zlbytes | zltail | zllen | entry1 | entry2 |  ...   | entryN | zlend |
            +---------+--------+-------+--------+--------+--------+--------+-------+
                                       ^                          ^        ^
address                                |                          |        |
                                ZIPLIST_ENTRY_HEAD                |   ZIPLIST_ENTRY_END
                                                                  |
                                                        ZIPLIST_ENTRY_TAIL
*/

/*
 * 增加 ziplist 的節點數
 *
 * T = O(1)
 */
#define ZIPLIST_INCR_LENGTH(zl,incr) { \
    if (ZIPLIST_LENGTH(zl) < UINT16_MAX) \
        ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))+incr); \
}

/*
 * 保存 ziplist 節點信息的結構
 */
typedef struct zlentry
{

    //前置節點的長度
    unsigned int prevrawlen;

    //編碼 prevrawlen 所需的字節大小
    unsigned int prevrawlensize;

    //當前節點值的長度
    unsigned int len;

    //編碼 len 所需的字節大小
    unsigned int lensize

    // 當前節點 header 的大小,等於 prevrawlensize + lensize
    unsigned int headersize;

    // 當前節點值所使用的編碼類型
    unsigned char encoding;

    // 指向當前節點的指針
    unsigned char *p;

} zlentry;

/*
 * 從 ptr 中取出節點值的編碼類型,並將它保存到 encoding 變量中。
 *
 * T = O(1)
 */
#define ZIP_ENTRY_ENCODING(ptr, encoding) do {  \
    (encoding) = (ptr[0]); \
    if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; \
} while(0)

/*
 * 返回保存 encoding 編碼的值所需的字節數量
 *
 * T = O(1)
 */
static 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;
}

/*
 * 編碼節點長度值 l ,並將它寫入到 p 中,然后返回編碼 l 所需的字節數量。
 *
 * 如果 p 為 NULL ,那么僅返回編碼 l 所需的字節數量,不進行寫入。
 *
 * T = O(1)
 */
static unsigned int zipEncodeLength(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 */
    // 將編碼后的長度寫入 p
    memcpy(p,buf,len);

    // 返回編碼所需的字節數
    return len;
}

/*
 * 解碼 ptr 指針,取出列表節點的相關信息,並將它們保存在以下變量中:
 *
 * - encoding 保存節點值的編碼類型。
 *
 * - lensize 保存編碼節點長度所需的字節數。
 *
 * - len 保存節點的長度。
 *
 * T = O(1)
 */
#define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do {                    \
                                                                               \
    /* 取出值的編碼類型 */                                                     \
    ZIP_ENTRY_ENCODING((ptr), (encoding));                                     \
                                                                               \
    /* 字符串編碼 */                                                           \
    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);

/*
 * 對前置節點的長度 len 進行編碼,並將它寫入到 p 中,
 * 然后返回編碼 len 所需的字節數量。
 *
 * 如果 p 為 NULL ,那么不進行寫入,僅返回編碼 len 所需的字節數量。
 *
 * T = O(1)
 */
static unsigned int zipPrevEncodeLength(unsigned char *p, unsigned int len)
{

    // 僅返回編碼 len 所需的字節數量
    if (p == NULL)
    {
        return (len < ZIP_BIGLEN) ? 1 : sizeof(len)+1;

        // 寫入並返回編碼 len 所需的字節數量
    }
    else
    {

        // 1 字節
        if (len < ZIP_BIGLEN)
        {
            p[0] = len;
            return 1;

            // 5 字節
        }
        else
        {
            // 添加 5 字節長度標識
            p[0] = ZIP_BIGLEN;
            // 寫入編碼
            memcpy(p+1,&len,sizeof(len));
            // 如果有必要的話,進行大小端轉換
            memrev32ifbe(p+1);
            // 返回編碼長度
            return 1+sizeof(len);
        }
    }
}

/*
 *
 * 將原本只需要 1 個字節來保存的前置節點長度 len 編碼至一個 5 字節長的 header 中。
 *
 * T = O(1)
 */
static void zipPrevEncodeLengthForceLarge(unsigned char *p, unsigned int len)
{

    if (p == NULL) return;


    // 設置 5 字節長度標識
    p[0] = ZIP_BIGLEN;

    // 寫入 len
    memcpy(p+1,&len,sizeof(len));
    memrev32ifbe(p+1);
}

/*
 * 解碼 ptr 指針,
 * 取出編碼前置節點長度所需的字節數,並將它保存到 prevlensize 變量中。
 *
 * T = O(1)
 */
#define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do {                          \
    if ((ptr)[0] < ZIP_BIGLEN) {                                               \
        (prevlensize) = 1;                                                     \
    } else {                                                                   \
        (prevlensize) = 5;                                                     \
    }                                                                          \
} while(0);

/*
 * 解碼 ptr 指針,
 * 取出編碼前置節點長度所需的字節數,
 * 並將這個字節數保存到 prevlensize 中。
 *
 * 然后根據 prevlensize ,從 ptr 中取出前置節點的長度值,
 * 並將這個長度值保存到 prevlen 變量中。
 *
 * T = O(1)
 */
#define ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen) do {                     \
                                                                               \
    /* 先計算被編碼長度值的字節數 */                                           \
    ZIP_DECODE_PREVLENSIZE(ptr, prevlensize);                                  \
                                                                               \
    /* 再根據編碼字節數來取出長度值 */                                         \
    if ((prevlensize) == 1) {                                                  \
        (prevlen) = (ptr)[0];                                                  \
    } else if ((prevlensize) == 5) {                                           \
        assert(sizeof((prevlensize)) == 4);                                    \
        memcpy(&(prevlen), ((char*)(ptr)) + 1, 4);                             \
        memrev32ifbe(&prevlen);                                                \
    }                                                                          \
} while(0);

/*
 * 計算編碼新的前置節點長度 len 所需的字節數,
 * 減去編碼 p 原來的前置節點長度所需的字節數之差。
 *
 * T = O(1)
 */
static int zipPrevLenByteDiff(unsigned char *p, unsigned int len)
{
    unsigned int prevlensize;

    // 取出編碼原來的前置節點長度所需的字節數
    // T = O(1)
    ZIP_DECODE_PREVLENSIZE(p, prevlensize);

    // 計算編碼 len 所需的字節數,然后進行減法運算
    // T = O(1)
    return zipPrevEncodeLength(NULL, len) - prevlensize;
}

/*
 * 返回指針 p 所指向的節點占用的字節數總和。
 *
 * T = O(1)
 */
static unsigned int zipRawEntryLength(unsigned char *p)
{
    unsigned int prevlensize, encoding, lensize, len;

    // 取出編碼前置節點的長度所需的字節數
    // T = O(1)
    ZIP_DECODE_PREVLENSIZE(p, prevlensize);

    // 取出當前節點值的編碼類型,編碼節點值長度所需的字節數,以及節點值的長度
    // T = O(1)
    ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);

    // 計算節點占用的字節數總和
    return prevlensize + lensize + len;
}

/* Check if string pointed to by 'entry' can be encoded as an integer.
 * Stores the integer value in 'v' and its encoding in 'encoding'.
 *
 * 檢查 entry 中指向的字符串能否被編碼為整數。
 *
 * 如果可以的話,
 * 將編碼后的整數保存在指針 v 的值中,並將編碼的方式保存在指針 encoding 的值中。
 *
 * 注意,這里的 entry 和前面代表節點的 entry 不是一個意思。
 *
 * T = O(N)
 */
static int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding)
{
    long long value;

    // 忽略太長或太短的字符串
    if (entrylen >= 32 || entrylen == 0) return 0;

    // 嘗試轉換
    // T = O(N)
    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. */
        // 轉換成功,以從小到大的順序檢查適合值 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;
}

/* Store integer 'value' at 'p', encoded as 'encoding'
 *
 * 以 encoding 指定的編碼方式,將整數值 value 寫入到 p 。
 *
 * T = O(1)
 */
static void zipSaveInteger(unsigned char *p, int64_t value, unsigned char encoding)
{
    int16_t i16;
    int32_t i32;
    int64_t i64;

    if (encoding == ZIP_INT_8B)
    {
        ((int8_t*)p)[0] = (int8_t)value;
    }
    else if (encoding == ZIP_INT_16B)
    {
        i16 = value;
        memcpy(p,&i16,sizeof(i16));
        memrev16ifbe(p);
    }
    else if (encoding == ZIP_INT_24B)
    {
        i32 = value<<8;
        memrev32ifbe(&i32);
        memcpy(p,((uint8_t*)&i32)+1,sizeof(i32)-sizeof(uint8_t));
    }
    else if (encoding == ZIP_INT_32B)
    {
        i32 = value;
        memcpy(p,&i32,sizeof(i32));
        memrev32ifbe(p);
    }
    else if (encoding == ZIP_INT_64B)
    {
        i64 = value;
        memcpy(p,&i64,sizeof(i64));
        memrev64ifbe(p);
    }
    else if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX)
    {
        /* Nothing to do, the value is stored in the encoding itself. */
    }
    else
    {
        assert(NULL);
    }
}

/* Read integer encoded as 'encoding' from 'p'
 *
 * 以 encoding 指定的編碼方式,讀取並返回指針 p 中的整數值。
 *
 * T = O(1)
 */
static int64_t zipLoadInteger(unsigned char *p, unsigned char encoding)
{
    int16_t i16;
    int32_t i32;
    int64_t i64, ret = 0;

    if (encoding == ZIP_INT_8B)
    {
        ret = ((int8_t*)p)[0];
    }
    else if (encoding == ZIP_INT_16B)
    {
        memcpy(&i16,p,sizeof(i16));
        memrev16ifbe(&i16);
        ret = i16;
    }
    else if (encoding == ZIP_INT_32B)
    {
        memcpy(&i32,p,sizeof(i32));
        memrev32ifbe(&i32);
        ret = i32;
    }
    else if (encoding == ZIP_INT_24B)
    {
        i32 = 0;
        memcpy(((uint8_t*)&i32)+1,p,sizeof(i32)-sizeof(uint8_t));
        memrev32ifbe(&i32);
        ret = i32>>8;
    }
    else if (encoding == ZIP_INT_64B)
    {
        memcpy(&i64,p,sizeof(i64));
        memrev64ifbe(&i64);
        ret = i64;
    }
    else if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX)
    {
        ret = (encoding & ZIP_INT_IMM_MASK)-1;
    }
    else
    {
        assert(NULL);
    }

    return ret;
}

/* Return a struct with all information about an entry.
 *
 * 將 p 所指向的列表節點的信息全部保存到 zlentry 中,並返回該 zlentry 。
 *
 * T = O(1)
 */
static zlentry zipEntry(unsigned char *p)
{
    zlentry e;

    // e.prevrawlensize 保存着編碼前一個節點的長度所需的字節數
    // e.prevrawlen 保存着前一個節點的長度
    // T = O(1)
    ZIP_DECODE_PREVLEN(p, e.prevrawlensize, e.prevrawlen);

    // p + e.prevrawlensize 將指針移動到列表節點本身
    // e.encoding 保存着節點值的編碼類型
    // e.lensize 保存着編碼節點值長度所需的字節數
    // e.len 保存着節點值的長度
    // T = O(1)
    ZIP_DECODE_LENGTH(p + e.prevrawlensize, e.encoding, e.lensize, e.len);

    // 計算頭結點的字節數
    e.headersize = e.prevrawlensize + e.lensize;

    // 記錄指針
    e.p = p;

    return e;
}

/* Create a new empty ziplist.
 *
 * 創建並返回一個新的 ziplist
 *
 * T = O(1)
 */
unsigned char *ziplistNew(void)
{

    // ZIPLIST_HEADER_SIZE 是 ziplist 表頭的大小
    // 1 字節是表末端 ZIP_END 的大小
    unsigned int bytes = ZIPLIST_HEADER_SIZE+1;

    // 為表頭和表末端分配空間
    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;
}


/* Resize the ziplist.
 *
 * 調整 ziplist 的大小為 len 字節。
 *
 * 當 ziplist 原有的大小小於 len 時,擴展 ziplist 不會改變 ziplist 原有的元素。
 *
 * T = O(N)
 */
static unsigned char *ziplistResize(unsigned char *zl, unsigned int len)
{

    // 用 zrealloc ,擴展時不改變現有元素
    zl = zrealloc(zl,len);

    // 更新 bytes 屬性
    ZIPLIST_BYTES(zl) = intrev32ifbe(len);

    // 重新設置表末端
    zl[len-1] = ZIP_END;

    return zl;
}


/*
 * 連鎖更新
 *
 * 當將一個新節點添加到某個節點之前的時候,
 * 如果原節點的 header 空間不足以保存新節點的長度,
 * 那么就需要對原節點的 header 空間進行擴展(從 1 字節擴展到 5 字節)。
 *
 * 但是,當對原節點進行擴展之后,原節點的下一個節點的 prevlen 可能出現空間不足,
 * 這種情況在多個連續節點的長度都接近 ZIP_BIGLEN 時可能發生。
 *
 * 這個函數就用於檢查並修復后續節點的空間問題。
 *
 * 反過來說,
 * 因為節點的長度變小而引起的連續縮小也是可能出現的,
 * 不過,為了避免擴展-縮小-擴展-縮小這樣的情況反復出現(flapping,抖動),
 * 我們不處理這種情況,而是任由 prevlen 比所需的長度更長。
 *
 * 注意,程序的檢查是針對 p 的后續節點,而不是 p 所指向的節點。
 * 因為節點 p 在傳入之前已經完成了所需的空間擴展工作。
 *
 * T = O(N^2)
 */
static 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;

    // T = O(N^2)
    while (p[0] != ZIP_END)
    {

        // 將 p 所指向的節點的信息保存到 cur 結構中
        cur = zipEntry(p);
        // 當前節點的長度
        rawlen = cur.headersize + cur.len;
        // 計算編碼當前節點的長度所需的字節數
        // T = O(1)
        rawlensize = zipPrevEncodeLength(NULL,rawlen);

        /* Abort if there is no next entry. */
        // 如果已經沒有后續空間需要更新了,跳出
        if (p[rawlen] == ZIP_END) break;

        // 取出后續節點的信息,保存到 next 結構中
        // T = O(1)
        next = zipEntry(p+rawlen);

        /* 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". */
            // 執行到這里,表示 next 空間的大小不足以編碼 cur 的長度
            // 所以程序需要對 next 節點的(header 部分)空間進行擴展

            // 記錄 p 的偏移量
            offset = p-zl;
            // 計算需要增加的節點數量
            extra = rawlensize-next.prevrawlensize;
            // 擴展 zl 的大小
            // T = O(N)
            zl = ziplistResize(zl,curlen+extra);
            // 還原指針 p
            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. */
            // 當 next 節點不是表尾節點時,更新列表到表尾節點的偏移量
            //
            // 不用更新的情況(next 為表尾節點):
            //
            // |     | next |      ==>    |     | new next          |
            //       ^                          ^
            //       |                          |
            //     tail                        tail
            //
            // 需要更新的情況(next 不是表尾節點):
            //
            // | next |     |   ==>     | new next          |     |
            //        ^                        ^
            //        |                        |
            //    old tail                 old tail
            //
            // 更新之后:
            //
            // | new next          |     |
            //                     ^
            //                     |
            //                  new tail
            // T = O(1)
            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. */
            // 向后移動 cur 節點之后的數據,為 cur 的新 header 騰出空間
            //
            // 示例:
            //
            // | header | value |  ==>  | header |    | value |  ==>  | header      | value |
            //                                   |<-->|
            //                            為新 header 騰出的空間
            // T = O(N)
            memmove(np+rawlensize,
                    np+next.prevrawlensize,
                    curlen-noffset-next.prevrawlensize-1);
            // 將新的前一節點長度值編碼進新的 next 節點的 header
            // T = O(1)
            zipPrevEncodeLength(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. */
                // 執行到這里,說明 next 節點編碼前置節點的 header 空間有 5 字節
                // 而編碼 rawlen 只需要 1 字節
                // 但是程序不會對 next 進行縮小,
                // 所以這里只將 rawlen 寫入 5 字節的 header 中就算了。
                // T = O(1)
                zipPrevEncodeLengthForceLarge(p+rawlen,rawlen);
            }
            else
            {
                // 運行到這里,
                // 說明 cur 節點的長度正好可以編碼到 next 節點的 header 中
                // T = O(1)
                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.
 *
 * 從位置 p 開始,連續刪除 num 個節點。
 *
 * 函數的返回值為處理刪除操作之后的 ziplist 。
 *
 * T = O(N^2)
 */
static 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;

    // 計算被刪除節點總共占用的內存字節數
    // 以及被刪除節點的總個數
    // T = O(N)
    first = zipEntry(p);
    for (i = 0; p[0] != ZIP_END && i < num; i++)
    {
        p += zipRawEntryLength(p);
        deleted++;
    }

    // totlen 是所有被刪除節點總共占用的內存字節數
    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. */
            // 因為位於被刪除范圍之后的第一個節點的 header 部分的大小
            // 可能容納不了新的前置節點,所以需要計算新舊前置節點之間的字節數差
            // T = O(1)
            nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);
            // 如果有需要的話,將指針 p 后退 nextdiff 字節,為新 header 空出空間
            p -= nextdiff;
            // 將 first 的前置節點的長度編碼至 p 中
            // T = O(1)
            zipPrevEncodeLength(p,first.prevrawlen);

            /* Update offset for tail */
            // 更新到達表尾的偏移量
            // T = O(1)
            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. */
            // 如果被刪除節點之后,有多於一個節點
            // 那么程序需要將 nextdiff 記錄的字節數也計算到表尾偏移量中
            // 這樣才能讓表尾偏移量正確對齊表尾節點
            // T = O(1)
            tail = zipEntry(p);
            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 */
            // 從表尾向表頭移動數據,覆蓋被刪除節點的數據
            // T = O(N)
            memmove(first.p,p,
                    intrev32ifbe(ZIPLIST_BYTES(zl))-(p-zl)-1);
        }
        else
        {

            // 執行這里,表示被刪除節點之后已經沒有其他節點了

            /* The entire tail was deleted. No need to move memory. */
            // T = O(1)
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe((first.p-zl)-first.prevrawlen);
        }

        /* Resize and update length */
        // 縮小並更新 ziplist 的長度
        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 */
        // 如果 p 所指向的節點的大小已經變更,那么進行級聯更新
        // 檢查 p 之后的所有節點是否符合 ziplist 的編碼要求
        // T = O(N^2)
        if (nextdiff != 0)
            zl = __ziplistCascadeUpdate(zl,p);
    }

    return zl;
}
/* Insert item at "p". */
/*
 * 根據指針 p 所指定的位置,將長度為 slen 的字符串 s 插入到 zl 中。
 *
 * 函數的返回值為完成插入操作之后的 ziplist
 *
 * T = O(N^2)
 */
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen)
{
    // 記錄當前 ziplist 的長度
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, 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 entry, tail;

    /* Find out prevlen for the entry that is inserted. */
    if (p[0] != ZIP_END)
    {
        // 如果 p[0] 不指向列表末端,說明列表非空,並且 p 正指向列表的其中一個節點
        // 那么取出 p 所指向節點的信息,並將它保存到 entry 結構中
        // 然后用 prevlen 變量記錄前置節點的長度
        // (當插入新節點之后 p 所指向的節點就成了新節點的前置節點)
        // T = O(1)
        entry = zipEntry(p);
        prevlen = entry.prevrawlen;
    }
    else
    {
        // 如果 p 指向表尾末端,那么程序需要檢查列表是否為:
        // 1)如果 ptail 也指向 ZIP_END ,那么列表為空;
        // 2)如果列表不為空,那么 ptail 將指向列表的最后一個節點。
        unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
        if (ptail[0] != ZIP_END)
        {
            // 表尾節點為新節點的前置節點

            // 取出表尾節點的長度
            // T = O(1)
            prevlen = zipRawEntryLength(ptail);
        }
    }

    /* See if the entry can be encoded */
    // 嘗試看能否將輸入字符串轉換為整數,如果成功的話:
    // 1)value 將保存轉換后的整數值
    // 2)encoding 則保存適用於 value 的編碼方式
    // 無論使用什么編碼, reqlen 都保存節點值的長度
    // T = O(N)
    if (zipTryEncoding(s,slen,&value,&encoding))
    {
        /* '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. */
    // 計算編碼前置節點的長度所需的大小
    // T = O(1)
    reqlen += zipPrevEncodeLength(NULL,prevlen);
    // 計算編碼當前節點值所需的大小
    // T = O(1)
    reqlen += zipEncodeLength(NULL,encoding,slen);

    /* 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. */
    // 只要新節點不是被添加到列表末端,
    // 那么程序就需要檢查看 p 所指向的節點(的 header)能否編碼新節點的長度。
    // nextdiff 保存了新舊編碼之間的字節大小差,如果這個值大於 0
    // 那么說明需要對 p 所指向的節點(的 header )進行擴展
    // T = O(1)
    nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;

    /* Store offset because a realloc may change the address of zl. */
    // 因為重分配空間可能會改變 zl 的地址
    // 所以在分配之前,需要記錄 zl 到 p 的偏移量,然后在分配之后依靠偏移量還原 p
    offset = p-zl;
    // curlen 是 ziplist 原來的長度
    // reqlen 是整個新節點的長度
    // nextdiff 是新節點的后繼節點擴展 header 的長度(要么 0 字節,要么 4 個字節)
    // T = O(N)
    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 */
        // 移動現有元素,為新元素的插入空間騰出位置
        // T = O(N)
        memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);

        /* Encode this entry's raw length in the next entry. */
        // 將新節點的長度編碼至后置節點
        // p+reqlen 定位到后置節點
        // reqlen 是新節點的長度
        // T = O(1)
        zipPrevEncodeLength(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. */
        // 如果新節點的后面有多於一個節點
        // 那么程序需要將 nextdiff 記錄的字節數也計算到表尾偏移量中
        // 這樣才能讓表尾偏移量正確對齊表尾節點
        // T = O(1)
        tail = zipEntry(p+reqlen);
        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 */
    // 當 nextdiff != 0 時,新節點的后繼節點的(header 部分)長度已經被改變,
    // 所以需要級聯地更新后續的節點
    if (nextdiff != 0)
    {
        offset = p-zl;
        // T  = O(N^2)
        zl = __ziplistCascadeUpdate(zl,p+reqlen);
        p = zl+offset;
    }

    /* Write the entry */
    // 一切搞定,將前置節點的長度寫入新節點的 header
    p += zipPrevEncodeLength(p,prevlen);
    // 將節點值的長度寫入新節點的 header
    p += zipEncodeLength(p,encoding,slen);
    // 寫入節點值
    if (ZIP_IS_STR(encoding))
    {
        // T = O(N)
        memcpy(p,s,slen);
    }
    else
    {
        // T = O(1)
        zipSaveInteger(p,value,encoding);
    }

    // 更新列表的節點數量計數器
    // T = O(1)
    ZIPLIST_INCR_LENGTH(zl,1);

    return zl;
}

/*
 * 將長度為 slen 的字符串 s 推入到 zl 中。
 *
 * where 參數的值決定了推入的方向:
 * - 值為 ZIPLIST_HEAD 時,將新值推入到表頭。
 * - 否則,將新值推入到表末端。
 *
 * 函數的返回值為添加新值后的 ziplist 。
 *
 * T = O(N^2)
 */
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where)
{

    // 根據 where 參數的值,決定將值推入到表頭還是表尾
    unsigned char *p;
    p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);

    // 返回添加新值后的 ziplist
    // T = O(N^2)
    return __ziplistInsert(zl,p,s,slen);
}

/* 
 * 根據給定索引,遍歷列表,並返回索引指定節點的指針。
 *
 * 如果索引為正,那么從表頭向表尾遍歷。
 * 如果索引為負,那么從表尾向表頭遍歷。
 * 正數索引從 0 開始,負數索引從 -1 開始。
 *
 * 如果索引超過列表的節點數量,或者列表為空,那么返回 NULL 。
 *
 * T = O(N)
 */
unsigned char *ziplistIndex(unsigned char *zl, int index)
{

    unsigned char *p;

    zlentry entry;

    // 處理負數索引
    if (index < 0)
    {

        // 將索引轉換為正數
        index = (-index)-1;

        // 定位到表尾節點
        p = ZIPLIST_ENTRY_TAIL(zl);

        // 如果列表不為空,那么。。。
        if (p[0] != ZIP_END)
        {

            // 從表尾向表頭遍歷
            entry = zipEntry(p);
            // T = O(N)
            while (entry.prevrawlen > 0 && index--)
            {
                // 前移指針
                p -= entry.prevrawlen;
                // T = O(1)
                entry = zipEntry(p);
            }
        }

        // 處理正數索引
    }
    else
    {

        // 定位到表頭節點
        p = ZIPLIST_ENTRY_HEAD(zl);

        // T = O(N)
        while (p[0] != ZIP_END && index--)
        {
            // 后移指針
            // T = O(1)
            p += zipRawEntryLength(p);
        }
    }

    // 返回結果
    return (p[0] == ZIP_END || index > 0) ? NULL : p;
}

/* 
 * 返回 p 所指向節點的后置節點。
 *
 * 如果 p 為表末端,或者 p 已經是表尾節點,那么返回 NULL 。
 *
 * T = O(1)
 */
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p)
{
    ((void) zl);

    // p 已經指向列表末端
    if (p[0] == ZIP_END)
    {
        return NULL;
    }

    // 指向后一節點
    p += zipRawEntryLength(p);
    if (p[0] == ZIP_END)
    {
        // p 已經是表尾節點,沒有后置節點
        return NULL;
    }

    return p;
}

/*
 * 返回 p 所指向節點的前置節點。
 *
 * 如果 p 所指向為空列表,或者 p 已經指向表頭節點,那么返回 NULL 。
 *
 * T = O(1)
 */
unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p)
{
    zlentry entry;

    // 如果 p 指向列表末端(列表為空,或者剛開始從表尾向表頭迭代)
    // 那么嘗試取出列表尾端節點
    if (p[0] == ZIP_END)
    {
        p = ZIPLIST_ENTRY_TAIL(zl);
        // 尾端節點也指向列表末端,那么列表為空
        return (p[0] == ZIP_END) ? NULL : p;

        // 如果 p 指向列表頭,那么說明迭代已經完成
    }
    else if (p == ZIPLIST_ENTRY_HEAD(zl))
    {
        return NULL;

        // 既不是表頭也不是表尾,從表尾向表頭移動指針
    }
    else
    {
        // 計算前一個節點的節點數
        entry = zipEntry(p);
        assert(entry.prevrawlen > 0);
        // 移動指針,指向前一個節點
        return p-entry.prevrawlen;
    }
}

/*
 * 取出 p 所指向節點的值:
 *
 * - 如果節點保存的是字符串,那么將字符串值指針保存到 *sstr 中,字符串長度保存到 *slen
 *
 * - 如果節點保存的是整數,那么將整數保存到 *sval
 *
 * 程序可以通過檢查 *sstr 是否為 NULL 來檢查值是字符串還是整數。
 *
 * 提取值成功返回 1 ,
 * 如果 p 為空,或者 p 指向的是列表末端,那么返回 0 ,提取值失敗。
 *
 * T = O(1)
 */
unsigned int ziplistGet(unsigned char *p, unsigned char **sstr, unsigned int *slen, long long *sval)
{

    zlentry entry;
    if (p == NULL || p[0] == ZIP_END) return 0;
    if (sstr) *sstr = NULL;

    // 取出 p 所指向的節點的各項信息,並保存到結構 entry 中
    // T = O(1)
    entry = zipEntry(p);

    // 節點的值為字符串,將字符串長度保存到 *slen ,字符串保存到 *sstr
    // T = O(1)
    if (ZIP_IS_STR(entry.encoding))
    {
        if (sstr)
        {
            *slen = entry.len;
            *sstr = p+entry.headersize;
        }

        // 節點的值為整數,解碼值,並將值保存到 *sval
        // T = O(1)
    }
    else
    {
        if (sval)
        {
            *sval = zipLoadInteger(p+entry.headersize,entry.encoding);
        }
    }

    return 1;
}

/* Insert an entry at "p".
 *
 * 將包含給定值 s 的新節點插入到給定的位置 p 中。
 *
 * 如果 p 指向一個節點,那么新節點將放在原有節點的前面。
 *
 * T = O(N^2)
 */
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen)
{
    return __ziplistInsert(zl,p,s,slen);
}

/*
 * 從 zl 中刪除 *p 所指向的節點,
 * 並且原地更新 *p 所指向的位置,使得可以在迭代列表的過程中對節點進行刪除。
 *
 * T = O(N^2)
 */
unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p)
{

    // 因為 __ziplistDelete 時會對 zl 進行內存重分配
    // 而內存充分配可能會改變 zl 的內存地址
    // 所以這里需要記錄到達 *p 的偏移量
    // 這樣在刪除節點之后就可以通過偏移量來將 *p 還原到正確的位置
    size_t offset = *p-zl;
    zl = __ziplistDelete(zl,*p,1);
    
    *p = zl+offset;

    return zl;
}

/* 
 * 從 index 索引指定的節點開始,連續地從 zl 中刪除 num 個節點。
 *
 * T = O(N^2)
 */
unsigned char *ziplistDeleteRange(unsigned char *zl, unsigned int index, unsigned int num)
{

    // 根據索引定位到節點
    // T = O(N)
    unsigned char *p = ziplistIndex(zl,index);

    // 連續刪除 num 個節點
    // T = O(N^2)
    return (p == NULL) ? zl : __ziplistDelete(zl,p,num);
}

/*
 * 將 p 所指向的節點的值和 sstr 進行對比。
 *
 * 如果節點值和 sstr 的值相等,返回 1 ,不相等則返回 0 。
 *
 * T = O(N)
 */
unsigned int ziplistCompare(unsigned char *p, unsigned char *sstr, unsigned int slen)
{
    zlentry entry;
    unsigned char sencoding;
    long long zval, sval;
    if (p[0] == ZIP_END) return 0;

    // 取出節點
    entry = zipEntry(p);
    if (ZIP_IS_STR(entry.encoding))
    {

        // 節點值為字符串,進行字符串對比

        /* Raw compare */
        if (entry.len == slen)
        {
            // T = O(N)
            return memcmp(p+entry.headersize,sstr,slen) == 0;
        }
        else
        {
            return 0;
        }
    }
    else
    {

        // 節點值為整數,進行整數對比

        if (zipTryEncoding(sstr,slen,&sval,&sencoding))
        {
            // T = O(1)
            zval = zipLoadInteger(p+entry.headersize,entry.encoding);
            return zval == sval;
        }
    }

    return 0;
}

/* 
 * 尋找節點值和 vstr 相等的列表節點,並返回該節點的指針。
 *
 *
 * 每次比對之前都跳過 skip 個節點。
 *
 *
 * 如果找不到相應的節點,則返回 NULL 。
 *
 * T = O(N^2)
 */
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;

    // 只要未到達列表末端,就一直迭代
    // T = O(N^2)
    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 */
            // 對比字符串值
            // T = O(N)
            if (ZIP_IS_STR(encoding))
            {
                if (len == vlen && memcmp(q, vstr, vlen) == 0)
                {
                    return p;
                }
            }
            else
            {
                // 因為傳入值有可能被編碼了,
                // 所以當第一次進行值對比時,程序會對傳入值進行解碼
                // 這個解碼操作只會進行一次
                if (vencoding == 0)
                {
                    if (!zipTryEncoding(vstr, vlen, &vll, &vencoding))
                    {
                        vencoding = UCHAR_MAX;
                    }
                    /* Must be non-zero by now */
                    assert(vencoding);
                }

                // 對比整數值
                if (vencoding != UCHAR_MAX)
                {
                    // T = O(1)
                    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;
}

/* Return length of ziplist.
 *
 * 返回 ziplist 中的節點個數
 *
 * T = O(N)
 */
unsigned int ziplistLen(unsigned char *zl)
{

    unsigned int len = 0;

    // 節點數小於 UINT16_MAX
    // T = O(1)
    if (intrev16ifbe(ZIPLIST_LENGTH(zl)) < UINT16_MAX)
    {
        len = intrev16ifbe(ZIPLIST_LENGTH(zl));

        // 節點數大於 UINT16_MAX 時,需要遍歷整個列表才能計算出節點數
        // T = O(N)
    }
    else
    {
        unsigned char *p = zl+ZIPLIST_HEADER_SIZE;
        while (*p != ZIP_END)
        {
            p += zipRawEntryLength(p);
            len++;
        }

        /* Re-store length if small enough */
        if (len < UINT16_MAX) ZIPLIST_LENGTH(zl) = intrev16ifbe(len);
    }

    return len;
}

/* Return ziplist blob size in bytes.
 *
 * 返回整個 ziplist 占用的內存字節數
 *
 * T = O(1)
 */
size_t ziplistBlobLen(unsigned char *zl)
{
    return intrev32ifbe(ZIPLIST_BYTES(zl));
}

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

    printf(
        "{total bytes %d} "
        "{length %u}\n"
        "{tail offset %u}\n",
        intrev32ifbe(ZIPLIST_BYTES(zl)),
        intrev16ifbe(ZIPLIST_LENGTH(zl)),
        intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)));
    p = ZIPLIST_ENTRY_HEAD(zl);
    while(*p != ZIP_END)
    {
        entry = zipEntry(p);
        printf(
            "{"
            "addr 0x%08lx, "
            "index %2d, "
            "offset %5ld, "
            "rl: %5u, "
            "hs %2u, "
            "pl: %5u, "
            "pls: %2u, "
            "payload %5u"
            "} ",
            (long unsigned)p,
            index,
            (unsigned long) (p-zl),
            entry.headersize+entry.len,
            entry.headersize,
            entry.prevrawlen,
            entry.prevrawlensize,
            entry.len);
        p += entry.headersize;
        if (ZIP_IS_STR(entry.encoding))
        {
            if (entry.len > 40)
            {
                if (fwrite(p,40,1,stdout) == 0) perror("fwrite");
                printf("...");
            }
            else
            {
                if (entry.len &&
                        fwrite(p,entry.len,1,stdout) == 0) perror("fwrite");
            }
        }
        else
        {
            printf("%lld", (long long) zipLoadInteger(p,entry.encoding));
        }
        printf("\n");
        p += entry.len;
        index++;
    }
    printf("{end}\n\n");
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM