redis6.0.5之quicklist阅读笔记2--函数解析

********************************************************************************************
********************************************************************************************
/* Optimization levels for size-based filling */ 基于大小填充的优化等级
static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};

/* Maximum size in bytes of any multi-element ziplist.
 * Larger values will live in their own isolated ziplists. */
多元素的压缩列表的最大字节数
更大的值将保存在他们独立的压缩列表中
#define SIZE_SAFETY_LIMIT 8192

/* Minimum ziplist size in bytes for attempting compression. */
尝试压缩的最小压缩列表的字节数
#define MIN_COMPRESS_BYTES 48

/* Minimum size reduction in bytes to store compressed quicklistNode data.
 * This also prevents us from storing compression if the compression
 * resulted in a larger size than the original data. */
最小减少的字节大小 用来保存快排节点的压缩数据的依据。
这个也住址了我们保存压缩数如果压缩的结果比原结果还要大
#define MIN_COMPRESS_IMPROVE 8  至少比原数据少8个字节

/* If not verbose testing, remove all debug printing. */  如果不是详细测试,移除所有的调试打印
#ifndef REDIS_TEST_VERBOSE
#define D(...)
#else
#define D(...)                                                                 \
    do {                                                                       \
        printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__);               \
        printf(__VA_ARGS__);                                                   \
        printf("\n");                                                          \
    } while (0);
#endif

/* Bookmarks forward declarations */ 书签转发申明
#define QL_MAX_BM ((1 << QL_BM_BITS)-1)  最大的书签数目
quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name); 通过名字查找书签
quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node); 通过节点查找书签
void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm);  删除书签

/* Simple way to give quicklistEntry structs default values with one call. */
简单方法只需一次调用设置 快排实体结构的默认值(初始化)
#define initEntry(e)                                                           \
    do {                                                                       \
        (e)->zi = (e)->value = NULL;                                           \
        (e)->longval = -123456789;                                             \
        (e)->quicklist = NULL;                                                 \
        (e)->node = NULL;                                                      \
        (e)->offset = 123456789;                                               \
        (e)->sz = 0;                                                           \
    } while (0)

#if __GNUC__ >= 3 
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#else
#define likely(x) (x)
#define unlikely(x) (x)
#endif
*************************************************************************************************************
/* Create a new quicklist.  Free with quicklistRelease(). */  创建一个新的列表,用函数quicklistRelease释放
quicklist *quicklistCreate(void) {
    struct quicklist *quicklist;

    quicklist = zmalloc(sizeof(*quicklist));  分配快排的空间
    quicklist->head = quicklist->tail = NULL;  指向空
    quicklist->len = 0;
    quicklist->count = 0;
    quicklist->compress = 0;
    quicklist->fill = -2;
    quicklist->bookmark_count = 0;
    return quicklist;
}
*************************************************************************************************************
#define COMPRESS_MAX ((1 << QL_COMP_BITS)-1)   最大压缩深度  2^16 -1
void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
    if (compress > COMPRESS_MAX) { 超过最大值,设置最大值
        compress = COMPRESS_MAX;
    } else if (compress < 0) { 小于0,设置0
        compress = 0;
    }
    quicklist->compress = compress; 中间区域,设置给定值
}
*************************************************************************************************************
#define FILL_MAX ((1 << (QL_FILL_BITS-1))-1) 最大填充值 2^16 -1
void quicklistSetFill(quicklist *quicklist, int fill) {
    if (fill > FILL_MAX) {
        fill = FILL_MAX;
    } else if (fill < -5) {
        fill = -5;
    }
    quicklist->fill = fill;
}
*************************************************************************************************************
设置可选功能
void quicklistSetOptions(quicklist *quicklist, int fill, int depth) { 
    quicklistSetFill(quicklist, fill);
    quicklistSetCompressDepth(quicklist, depth);
}
*************************************************************************************************************
/* Create a new quicklist with some default parameters. */ 用默认参数创建一个新的快排列表
quicklist *quicklistNew(int fill, int compress) {
    quicklist *quicklist = quicklistCreate();  创建快排列表
    quicklistSetOptions(quicklist, fill, compress);  设置选择功能
    return quicklist;
}
*************************************************************************************************************
创建快排节点
REDIS_STATIC quicklistNode *quicklistCreateNode(void) {
    quicklistNode *node;
    node = zmalloc(sizeof(*node)); 分配内存空间
    node->zl = NULL;
    node->count = 0;
    node->sz = 0;
    node->next = node->prev = NULL;
    node->encoding = QUICKLIST_NODE_ENCODING_RAW; 不压缩
    node->container = QUICKLIST_NODE_CONTAINER_ZIPLIST; 用压缩列表
    node->recompress = 0;
    return node;
}
*************************************************************************************************************
/* Return cached quicklist count */  返回快排列表元素个数
unsigned long quicklistCount(const quicklist *ql) { return ql->count; }
*************************************************************************************************************
/* Free entire quicklist. */ 释放整个列表
void quicklistRelease(quicklist *quicklist) {
    unsigned long len;
    quicklistNode *current, *next;

    current = quicklist->head;  指向头部
    len = quicklist->len; 总长度
    while (len--) {  对每个节点
        next = current->next;

        zfree(current->zl);释放当前节点的列表
        quicklist->count -= current->count; 总元素个数 需要减去当前释放的元素个数

        zfree(current); 释放当前节点

        quicklist->len--; 快排列表长度减去1
        current = next; 将下个节点赋给当前节点
    }
    quicklistBookmarksClear(quicklist); 清除快排列表的书签
    zfree(quicklist); 释放快排列表
}
*************************************************************************************************************
/* Compress the ziplist in 'node' and update encoding details.
 * Returns 1 if ziplist compressed successfully.
 * Returns 0 if compression failed or if ziplist too small to compress. */
在节点出压缩压缩列表并且更新编码细节
返回1如果压缩列表压缩成功
返回0如果压缩失败或者压缩列表太小不用压缩
REDIS_STATIC int __quicklistCompressNode(quicklistNode *node) {
#ifdef REDIS_TEST
    node->attempted_compress = 1;
#endif

    /* Don't bother compressing small values */ 不用可以压缩短的值
    if (node->sz < MIN_COMPRESS_BYTES)  如果小于48个字节,那么不用压缩
        return 0;

    quicklistLZF *lzf = zmalloc(sizeof(*lzf) + node->sz);  分配结构体的长度 + 数据的长度

    /* Cancel if compression fails or doesn't compress small enough */ 如果压缩失败或者不能压缩的足够小
    if (((lzf->sz = lzf_compress(node->zl, node->sz, lzf->compressed,
                                 node->sz)) == 0) ||  压缩失败
        lzf->sz + MIN_COMPRESS_IMPROVE >= node->sz) { 压缩之后的长度加上8个字节 比原长度长, 也没有必要压缩
        /* lzf_compress aborts/rejects compression if value not compressable. */ 如果值没有压缩,那么就放弃/拒绝lzf压缩
        zfree(lzf);
        return 0;
    }
    lzf = zrealloc(lzf, sizeof(*lzf) + lzf->sz); 根据压缩的新长度重新分配内存
    zfree(node->zl); 释放原来的压缩列表
    node->zl = (unsigned char *)lzf; 指向新的压缩后的数据
    node->encoding = QUICKLIST_NODE_ENCODING_LZF;设置节点的编码类型为压缩类型
    node->recompress = 0; 没有再次压缩
    return 1;
}
*************************************************************************************************************
/* Compress only uncompressed nodes. */ 压缩只针对非压缩节点
#define quicklistCompressNode(_node)                                           \
    do {                                                                       \
        if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_RAW) {     \
            __quicklistCompressNode((_node));                                  \
        }                                                                      \
    } while (0)
*************************************************************************************************************
/* Uncompress the ziplist in 'node' and update encoding details.
 * Returns 1 on successful decode, 0 on failure to decode. */
解压缩 压缩列表的节点更新编码细节
如果解码成功返回1, 解码失败返回0
REDIS_STATIC int __quicklistDecompressNode(quicklistNode *node) {
#ifdef REDIS_TEST
    node->attempted_compress = 0;
#endif

    void *decompressed = zmalloc(node->sz);  分配解压后的空间,原长度
    quicklistLZF *lzf = (quicklistLZF *)node->zl; 获取压缩数据
    if (lzf_decompress(lzf->compressed, lzf->sz, decompressed, node->sz) == 0) {
        /* Someone requested decompress, but we can't decompress.  Not good. */
        一些人请求解压,但是我们不能解压,因为解压失败
        zfree(decompressed); 释放申请的空间
        return 0;
    }
    zfree(lzf); 解压成功,释放压缩结构
    node->zl = decompressed; 指向解压出来的数据
    node->encoding = QUICKLIST_NODE_ENCODING_RAW;编码还原成无编码
    return 1;
}
*************************************************************************************************************
/* Decompress only compressed nodes. */ 只解压 压缩的节点
#define quicklistDecompressNode(_node)                                         \
    do {                                                                       \
        if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_LZF) {     \
            __quicklistDecompressNode((_node));                                \
        }                                                                      \
    } while (0)
*************************************************************************************************************
/* Force node to not be immediately re-compresable */ 强制节点不应该立即被重新压缩
#define quicklistDecompressNodeForUse(_node)                                   \
    do {                                                                       \
        if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_LZF) {     \
            __quicklistDecompressNode((_node));                                \
            (_node)->recompress = 1;                                           \
        }                                                                      \
    } while (0)
*************************************************************************************************************
/* Extract the raw LZF data from this quicklistNode.
 * Pointer to LZF data is assigned to '*data'.
 * Return value is the length of compressed LZF data. */
从快排列表节点中抽取原始的LZF数据,指针指向LZF的数据被赋给*data。返回值是压缩的LZF数据的长度
size_t quicklistGetLzf(const quicklistNode *node, void **data) {
    quicklistLZF *lzf = (quicklistLZF *)node->zl;
    *data = lzf->compressed;
    return lzf->sz;
}
*************************************************************************************************************
#define quicklistAllowsCompression(_ql) ((_ql)->compress != 0)  定义列表是否允许被压缩

/* Force 'quicklist' to meet compression guidelines set by compress depth.
 * The only way to guarantee interior nodes get compressed is to iterate
 * to our "interior" compress depth then compress the next node we find.
 * If compress depth is larger than the entire list, we return immediately. */
通过压缩的深度强制快排列表达到压缩的指导规则,确保内部节点得到压缩的唯一的方法是通过迭代到我们内部的压缩深度,
然后压缩下个我们找到的节点。如果压缩深度已经大于整个列表,我们立即返回。
REDIS_STATIC void __quicklistCompress(const quicklist *quicklist,
                                      quicklistNode *node) {
    /* If length is less than our compress depth (from both sides), 如果长度小于我们的压缩深度(从两端开始,我们不压缩
     * we can't compress anything. */
    if (!quicklistAllowsCompression(quicklist) ||  如果不允许压缩(压缩深度为0)
        quicklist->len < (unsigned int)(quicklist->compress * 2)) 或者列表长度小于压缩深度的两倍 直接返回
        return;
#if 0
    /* Optimized cases for small depth counts */  对于小的深度计数优化例子
    if (quicklist->compress == 1) {  如果压缩深度为1
        quicklistNode *h = quicklist->head, *t = quicklist->tail;
        quicklistDecompressNode(h); 解压缩头
        quicklistDecompressNode(t);  解压缩尾
        if (h != node && t != node)
            quicklistCompressNode(node);压缩这个节点
        return;
    } else if (quicklist->compress == 2) { 如果压缩深度为2
        quicklistNode *h = quicklist->head, *hn = h->next, *hnn = hn->next;
        quicklistNode *t = quicklist->tail, *tp = t->prev, *tpp = tp->prev;
        quicklistDecompressNode(h);
        quicklistDecompressNode(hn);
        quicklistDecompressNode(t);
        quicklistDecompressNode(tp);
        if (h != node && hn != node && t != node && tp != node) {
            quicklistCompressNode(node);
        }
        if (hnn != t) {
            quicklistCompressNode(hnn);
        }
        if (tpp != h) {
            quicklistCompressNode(tpp);
        }
        return;
    }
#endif

    /* Iterate until we reach compress depth for both sides of the list.a
     * Note: because we do length checks at the *top* of this function,
     *       we can skip explicit null checks below. Everything exists. */
从列表的两端,迭代直到我们到达压缩的深度。
注意: 因为做长度检查是在 是在函数的头部做的,我们能够跳过下面明确的空检查。任何事情都存在
    quicklistNode *forward = quicklist->head; 指向头部节点
    quicklistNode *reverse = quicklist->tail; 指向尾部节点
    int depth = 0; 深度为0
    int in_depth = 0;
    while (depth++ < quicklist->compress) {  当小于预定深度时,继续下一个节点
        quicklistDecompressNode(forward);  从正向解压缩节点
        quicklistDecompressNode(reverse);  从反向解压缩节点

        if (forward == node || reverse == node)   要压缩的节点已经找到,设置标志
            in_depth = 1;

        if (forward == reverse) 当正向和反向指向同一个节点时候,就结束了
            return;

        forward = forward->next;  正向下一个
        reverse = reverse->prev;  反向下一个
    }

    if (!in_depth)  在深度规则之内列表没有找到节点,压缩节点
        quicklistCompressNode(node);

    if (depth > 2) {  
    读到这里觉得这个条件奇怪,果然这里的判断条件可以去掉,详细见
    https://github.com/redis/redis/commit/9b4edfdf08a99adda0b6da5b1434d14884d6419b
        /* At this point, forward and reverse are one node beyond depth */
        在这种情况下,正向和反向都解压超过了一个节点的深度
        quicklistCompressNode(forward); 将解压的节点重新压缩回去
        quicklistCompressNode(reverse);
    }
}
*************************************************************************************************************
#define quicklistCompress(_ql, _node)                                          \
    do {                                                                       \
        if ((_node)->recompress)                                               \
            quicklistCompressNode((_node));                                    \
        else                                                                   \
            __quicklistCompress((_ql), (_node));                               \
    } while (0)

/* If we previously used quicklistDecompressNodeForUse(), just recompress. */
#define quicklistRecompressOnly(_ql, _node)                                    \
    do {                                                                       \
        if ((_node)->recompress)                                               \
            quicklistCompressNode((_node));                                    \
    } while (0)
*************************************************************************************************************
/* Insert 'new_node' after 'old_node' if 'after' is 1.
 * Insert 'new_node' before 'old_node' if 'after' is 0.
 * Note: 'new_node' is *always* uncompressed, so if we assign it to
 *       head or tail, we do not need to uncompress it. */
插入新节点 在旧节点 之后,如果after的值为1
插入新值 在旧节点之前,如果after的值为0
注意: 新节点总是不压缩的,所以如果我们将它设置到头部或者尾部,我们不需要解压它
REDIS_STATIC void __quicklistInsertNode(quicklist *quicklist,
                                        quicklistNode *old_node,
                                        quicklistNode *new_node, int after) {
    if (after) { 如果插在旧节点后面
        new_node->prev = old_node; 前置节点就是旧节点
        if (old_node) { 如果旧节点不为空,那么旧节点的下个节点 就是新节点的下个节点
            new_node->next = old_node->next;
            if (old_node->next) 如果旧节点的下个节点不为空,那么下个节点的前置节点就变为新插入的节点了
                old_node->next->prev = new_node;
            old_node->next = new_node; 旧节点的下个节点就变为新插入的节点
        }
        if (quicklist->tail == old_node) 如果旧节点是原列表的最后一个节点
            quicklist->tail = new_node; 那么新插入的节点就便成为新的最后一个节点了
    } else { 如果插在旧节点前面
        new_node->next = old_node; 新插入节点的下个节点就是 旧节点
        if (old_node) { 旧节点不为空
            new_node->prev = old_node->prev; 旧节点的前置节点就是新插入节点的前置节点
            if (old_node->prev) 如果旧节点的前置节点不为空
                old_node->prev->next = new_node; 那么旧节点的前置节点的后面就是新插入节点了
            old_node->prev = new_node; 旧节点的前置节点 更换为 新插入节点
        }
        if (quicklist->head == old_node) 如果旧节点是头节点
            quicklist->head = new_node; 那么新插入的节点就变为新的头节点了
    }
    /* If this insert creates the only element so far, initialize head/tail. */
    如果列表原来是空的,新插入的节点是第一个节点,那么需要初始化头和尾节点
    if (quicklist->len == 0) {
        quicklist->head = quicklist->tail = new_node;
    }

    if (old_node) 如果旧节点不为空,压缩旧节点
        quicklistCompress(quicklist, old_node);

    quicklist->len++; 列表元素加1
}
*************************************************************************************************************
/* Wrappers for node inserting around existing node. */
封装在存在节点周围插入新节点
REDIS_STATIC void _quicklistInsertNodeBefore(quicklist *quicklist,
                                             quicklistNode *old_node,
                                             quicklistNode *new_node) {
    __quicklistInsertNode(quicklist, old_node, new_node, 0); 前面插入
}

REDIS_STATIC void _quicklistInsertNodeAfter(quicklist *quicklist,
                                            quicklistNode *old_node,
                                            quicklistNode *new_node) {
    __quicklistInsertNode(quicklist, old_node, new_node, 1); 后面插入
}
*************************************************************************************************************
static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};
REDIS_STATIC int
_quicklistNodeSizeMeetsOptimizationRequirement(const size_t sz,
                                               const int fill) {
    if (fill >= 0) 如果fill大于0,直接返回0
        return 0;

    size_t offset = (-fill) - 1; 偏移量
    if (offset < (sizeof(optimization_level) / sizeof(*optimization_level))) { 在范围内
        if (sz <= optimization_level[offset]) { 大小小于设定的优化值
            return 1;
        } else {
            return 0;
        }
    } else {
        return 0;
    }
}
*************************************************************************************************************
#define sizeMeetsSafetyLimit(sz) ((sz) <= SIZE_SAFETY_LIMIT)  大小应该小于设定的多元素的压缩列表最大值

REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node,
                                           const int fill, const size_t sz) {
    if (unlikely(!node)) 节点为空就退出,虽然这个概率很小,基本上不会走这个分支,但是也需要测试一下
        return 0;

    int ziplist_overhead;
    /* size of previous offset */  前面实体长度偏移量
    if (sz < 254)
        ziplist_overhead = 1;  小于254时候,只需要一个字节
    else
        ziplist_overhead = 5;

    /* size of forward offset */ 编码当前实体所需格式长度
    if (sz < 64)
        ziplist_overhead += 1;
    else if (likely(sz < 16384))
        ziplist_overhead += 2;
    else
        ziplist_overhead += 5;

    /* new_sz overestimates if 'sz' encodes to an integer type */ 如果sz被编码为整型值,那么new_sz会超出估计
    unsigned int new_sz = node->sz + sz + ziplist_overhead; 新的总长度
    if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(new_sz, fill)))  压缩列表长度是否超标,不超标允许插入
        return 1;
    else if (!sizeMeetsSafetyLimit(new_sz)) 新的字节数超过了固定的值,不允许插入
        return 0;
    else if ((int)node->count < fill) 当前元素个数小于传入优化参数时,返回允许
        return 1;
    else
        return 0;
}
*************************************************************************************************************
是否允许合并两个快排列表
REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a,
                                          const quicklistNode *b,
                                          const int fill) {
    if (!a || !b)  如果有个列表为空,就不用合并了
        return 0;

    /* approximate merged ziplist size (- 11 to remove one ziplist
     * header/trailer) */  合并压缩列表的近似大小(减去11是因为压缩列表的 一个列表的头和尾的格式,可以去掉)
    unsigned int merge_sz = a->sz + b->sz - 11; 合并后的大小
    if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(merge_sz, fill)))
        return 1;
    else if (!sizeMeetsSafetyLimit(merge_sz))
        return 0;
    else if ((int)(a->count + b->count) <= fill)
        return 1;
    else
        return 0;
}
*************************************************************************************************************
更新快排列表节点的字节计数
#define quicklistNodeUpdateSz(node)                                            \
    do {                                                                       \
        (node)->sz = ziplistBlobLen((node)->zl);                               \
    } while (0)
*************************************************************************************************************
/* Add new entry to head node of quicklist.
在快排列表的头节点增加一个实体
 * Returns 0 if used existing head. 返回0如果使用已经存在头
 * Returns 1 if new head created. */ 返回1 如果创建了新的头
int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
    quicklistNode *orig_head = quicklist->head; 获取列表的头节点
    if (likely(
            _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
        quicklist->head->zl =
            ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD); 从压缩列表头部插入一个实体
        quicklistNodeUpdateSz(quicklist->head); 更新快排列表节点的字节计数
    } else {
        quicklistNode *node = quicklistCreateNode(); 创建一个新的节点
        node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD); 从压缩列表头部插入一个实体

        quicklistNodeUpdateSz(node); 更新快排列表节点的字节计数
        _quicklistInsertNodeBefore(quicklist, quicklist->head, node); 在快排列表头部插入一个新节点
    }
    quicklist->count++; 快排节点计数加1
    quicklist->head->count++;快排新的头节点压缩列表的元素加1
    return (orig_head != quicklist->head);
}
*************************************************************************************************************
/* Add new entry to tail node of quicklist.
增加一个新的实体到快排列表的尾节点
 * Returns 0 if used existing tail. 返回0如果使用已经存在的尾巴节点
 * Returns 1 if new tail created. */ 返回1 如果创建了新尾巴节点
int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
    quicklistNode *orig_tail = quicklist->tail; 获取尾巴节点
    if (likely(
            _quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {
        quicklist->tail->zl =
            ziplistPush(quicklist->tail->zl, value, sz, ZIPLIST_TAIL);
        quicklistNodeUpdateSz(quicklist->tail);
    } else {
        quicklistNode *node = quicklistCreateNode();
        node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL);

        quicklistNodeUpdateSz(node);
        _quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
    }
    quicklist->count++;
    quicklist->tail->count++;
    return (orig_tail != quicklist->tail);
}
*************************************************************************************************************
/* Create new node consisting of a pre-formed ziplist.
 * Used for loading RDBs where entire ziplists have been stored
 * to be retrieved later. */
创建一个新节点 使用预先格式化好的压缩列表
void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl) {
    quicklistNode *node = quicklistCreateNode(); 创建压缩列表节点

    node->zl = zl;  指向创建好的压缩列表
    node->count = ziplistLen(node->zl); 压缩列表元素个数
    node->sz = ziplistBlobLen(zl); 压缩列表长度

    _quicklistInsertNodeAfter(quicklist, quicklist->tail, node); 在快排列表尾部插入新节点
    quicklist->count += node->count; 快排列表的新元素个数
}
*************************************************************************************************************
/* Append all values of ziplist 'zl' individually into 'quicklist'.
将压缩列表所有的值分别添加到快排列表
 * This allows us to restore old RDB ziplists into new quicklists
 * with smaller ziplist sizes than the saved RDB ziplist.
这个允许我们使用较小的压缩列表的大小去保存旧的RDB压缩列表到新的快排列表,对比直接保存RDB压缩列表
 * Returns 'quicklist' argument. Frees passed-in ziplist 'zl' */
返回传入的快排列表参数,释放传入的压缩列表
quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
                                            unsigned char *zl) {
    unsigned char *value;
    unsigned int sz;
    long long longval;
    char longstr[32] = {0};

    unsigned char *p = ziplistIndex(zl, 0);
    while (ziplistGet(p, &value, &sz, &longval)) {
        if (!value) {不是字符串
            /* Write the longval as a string so we can re-add it */ 将数值转化为字符串写入添加
            sz = ll2string(longstr, sizeof(longstr), longval);
            value = (unsigned char *)longstr;
        }
        quicklistPushTail(quicklist, value, sz);在快排列表尾部节点插入一个实体元素
        p = ziplistNext(zl, p); 获取下个压缩列表的元素
    }
    zfree(zl); 释放传入压缩列表
    return quicklist;
}
*************************************************************************************************************
/* Create new (potentially multi-node) quicklist from a single existing ziplist.
从一个单一存在的压缩列表创建一个新的快排列表(潜在是多节点的)
 * Returns new quicklist.  Frees passed-in ziplist 'zl'. */
 返回新的快排列表,释放传入的压缩列表
quicklist *quicklistCreateFromZiplist(int fill, int compress,
                                      unsigned char *zl) {
    return quicklistAppendValuesFromZiplist(quicklistNew(fill, compress), zl);
}
*************************************************************************************************************
当快排列表的某个节点元素为0时,删除之
#define quicklistDeleteIfEmpty(ql, n)                                          \
    do {                                                                       \
        if ((n)->count == 0) {                                                 \
            __quicklistDelNode((ql), (n));                                     \
            (n) = NULL;                                                        \
        }                                                                      \
    } while (0)
*************************************************************************************************************
REDIS_STATIC void __quicklistDelNode(quicklist *quicklist,
                                     quicklistNode *node) {
    /* Update the bookmark if any */ 如果存在书签就更新它
    quicklistBookmark *bm = _quicklistBookmarkFindByNode(quicklist, node); 通过节点查找书签
    if (bm) {如果存在
        bm->node = node->next;把当前节点的下一个节点赋值给书签
        /* if the bookmark was to the last node, delete it. */ 如果书签节点就是最后一个节点,删除之
        if (!bm->node)
            _quicklistBookmarkDelete(quicklist, bm);
    }

    if (node->next) 当前节点的下一个节点不为空
        node->next->prev = node->prev; 把下一个节点的前置设置为当前节点的前置
    if (node->prev) 当前节点的前置节点不为空
        node->prev->next = node->next;当前节点的前置节点的下一个节点就是 当前节点的下一个节点

    if (node == quicklist->tail) {  如果当前节点是尾部节点,那么需要重新设置尾部节点
        quicklist->tail = node->prev;
    }

    if (node == quicklist->head) { 
        quicklist->head = node->next;
    }

    /* If we deleted a node within our compress depth, we
     * now have compressed nodes needing to be decompressed. */
     如果我们删除了一个在我们压缩深度之内的节点,我们需要重新压缩 将要被压缩的节点
     (就是要补充被删除的压缩节点,来维持压缩深度)
    __quicklistCompress(quicklist, NULL);

    quicklist->count -= node->count; 快排列表的总计数 需要减去 删除的节点元素计数

    zfree(node->zl); 释放节点指向压缩列表
    zfree(node); 释放节点本身
    quicklist->len--; 节点长度减去1
}
*************************************************************************************************************
/* Delete one entry from list given the node for the entry and a pointer
 * to the entry in the node. 
从为特定实体给定节点的列表中删除一个实体元素,同时设置一个指针指向节点中的实体元素
 * Note: quicklistDelIndex() *requires* uncompressed nodes because you
 *       already had to get *p from an uncompressed node somewhere.
注意: 函数quicklistDelIndex 需要不压缩的节点,因为你已经从一个不压缩节点的某处获取指针*p
(从已经获得*p,说明节点是不压缩的)
 * Returns 1 if the entire node was deleted, 0 if node still exists.
 返回1,如果这个实体所在的节点已经被删除,返回0如果节点还存在
 * Also updates in/out param 'p' with the next offset in the ziplist. */
同时用压缩列表中的下一个偏移量 来 更新 输入/输出 参数 p 
REDIS_STATIC int quicklistDelIndex(quicklist *quicklist, quicklistNode *node,
                                   unsigned char **p) {
    int gone = 0;

    node->zl = ziplistDelete(node->zl, p); 删除元素p
    node->count--; 节点计数减少1
    if (node->count == 0) { 如果节点数目为0,删除这个节点,否则只更新长度数据
        gone = 1;
        __quicklistDelNode(quicklist, node);
    } else {
        quicklistNodeUpdateSz(node); 更新长度数据
    }
    quicklist->count--; 快排列表的计数减少1
    /* If we deleted the node, the original node is no longer valid */
    如果我们删除了这个节点,那么原始的节点就不再有效
    return gone ? 1 : 0;
}
*************************************************************************************************************
/* Delete one element represented by 'entry'
删除一个由entry表示的元素
 * 'entry' stores enough metadata to delete the proper position in
 * the correct ziplist in the correct quicklist node. */
entry存储足够的元数据 来删除在正确快排列表节点的正确压缩列表的恰当的位置
void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) {
    quicklistNode *prev = entry->node->prev;  快排列表当前节点的前置节点
    quicklistNode *next = entry->node->next;  
    int deleted_node = quicklistDelIndex((quicklist *)entry->quicklist,
                                         entry->node, &entry->zi);

    /* after delete, the zi is now invalid for any future usage. */
    经过删除,指针zi对未来的使用已经不再有效
    iter->zi = NULL;

    /* If current node is deleted, we must update iterator node and offset. */
    如果当前节点被删除,那么我们必须更新迭代器的节点和偏移量
    if (deleted_node) {
        if (iter->direction == AL_START_HEAD) { 如果迭代器从头开始
            iter->current = next;迭代器指向下一个节点
            iter->offset = 0; 偏移量为0
        } else if (iter->direction == AL_START_TAIL) { 从尾部开始,反向
            iter->current = prev; 迭代器指向前一个节点
            iter->offset = -1; 偏移量为-1
        }
    }
    /* else if (!deleted_node), no changes needed.
    如果没有删除节点,那么什么也不需要做
     * we already reset iter->zi above, and the existing iter->offset
     * doesn't move again because:
我们已经在上面重新设置 迭代器的iter->zi值,并且存在的迭代器偏移量不需要重新移动,因为
     *   - [1, 2, 3] => delete offset 1 => [1, 3]: next element still offset 1 删除元素位置,不影响偏移量的位置
     *   - [1, 2, 3] => delete offset 0 => [2, 3]: next element still offset 0
     *  if we deleted the last element at offet N and now
     *  length of this ziplist is N-1, the next call into
     *  quicklistNext() will jump to the next node. */
     如果我们删除了最后一个偏移量为N的元素,那么现在压表的长度就是N-1,
     下次调用函数quicklistNext将会跳到下个快排列表节点所在
}
*************************************************************************************************************
/* Replace quicklist entry at offset 'index' by 'data' with length 'sz'.
在偏移量index,用长度为sz的数据data 取代快排列表实体
 * Returns 1 if replace happened. 返回1 如果取代成功
 * Returns 0 if replace failed and no changes happened. */ 返回0,如果取代失败并且什么也没有发生
int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
                            int sz) {
    quicklistEntry entry;
    if (likely(quicklistIndex(quicklist, index, &entry))) {  如果找到该快排列表的实体元素
        /* quicklistIndex provides an uncompressed node */quicklistIndex提供一个不压缩的节点
        entry.node->zl = ziplistDelete(entry.node->zl, &entry.zi); 删除原来的元素
        entry.node->zl = ziplistInsert(entry.node->zl, entry.zi, data, sz); 插入新的值
        quicklistNodeUpdateSz(entry.node); 更新快排列表节点的大小
        quicklistCompress(quicklist, entry.node); 压缩新的节点
        return 1;
    } else {
        return 0;
    }
}
*************************************************************************************************************
/* Given two nodes, try to merge their ziplists.
给定两个节点,尝试合并它们内部的压缩列表
 * This helps us not have a quicklist with 3 element ziplists if
 * our fill factor can handle much higher levels.
如果我们的填充因子能够处理更高层级,那么和这个会帮助我们不会拥有一个3个元素的压缩列表
 * Note: 'a' must be to the LEFT of 'b'.
注意: a必须在b的左边
 * After calling this function, both 'a' and 'b' should be considered
 * unusable.  The return value from this function must be used
 * instead of re-using any of the quicklistNode input arguments.
经过这个函数的调用,a和b应该被人为不能再使用。这个函数的返回值必须代替 重复使用任何输入的快排节点的参数  进行使用
(意思就是输入的参数全部作废,不能再使用了,要用就用函数返回的值)
 * Returns the input node picked to merge against or NULL if
 * merging was not possible. */
返回选择要合并的输入节点 或者空如果不能合并

REDIS_STATIC quicklistNode *_quicklistZiplistMerge(quicklist *quicklist,
                                                   quicklistNode *a,
                                                   quicklistNode *b) {
    D("Requested merge (a,b) (%u, %u)", a->count, b->count);

    quicklistDecompressNode(a); 解压缩输入节点a
    quicklistDecompressNode(b); 解压缩输入节点b
    if ((ziplistMerge(&a->zl, &b->zl))) { 合并两个节点的压缩列表
        /* We merged ziplists! Now remove the unused quicklistNode. */
        我们合并了压缩列表,现在移除不再使用的快排列表节点
        quicklistNode *keep = NULL, *nokeep = NULL;
        if (!a->zl) { 合并到b节点的压缩列表,a表为空,返回的是b表
            nokeep = a;
            keep = b;
        } else if (!b->zl) {
            nokeep = b;
            keep = a;
        }
        keep->count = ziplistLen(keep->zl); 新压缩列表的实体元素个数
        quicklistNodeUpdateSz(keep); 更新快排列表节点的长度

        nokeep->count = 0;
        __quicklistDelNode(quicklist, nokeep); 删除被合并的节点
        quicklistCompress(quicklist, keep);压缩新节点
        return keep;
    } else {
        /* else, the merge returned NULL and nothing changed. */
        合并失败返回空,没有东西被改变
        return NULL;
    }
}
*************************************************************************************************************
/* Attempt to merge ziplists within two nodes on either side of 'center'.
尝试在节点center两侧的合并两个节点的压缩列表
 * We attempt to merge:  我们尝试去合并 以下四个步骤
 *   - (center->prev->prev, center->prev)  当前节点的 前置  和  前置的前置 
 *   - (center->next, center->next->next)  当前节点下一个 和 下一个的下一个
 *   - (center->prev, center)  当前节点的前置 和 当前节点 
 *   - (center, center->next)  当前节点 和 当前节点的下一个节点
 */
REDIS_STATIC void _quicklistMergeNodes(quicklist *quicklist,
                                       quicklistNode *center) {
    int fill = quicklist->fill;
    quicklistNode *prev, *prev_prev, *next, *next_next, *target;
    prev = prev_prev = next = next_next = target = NULL; 初始化为空

    if (center->prev) {  如果当前节点的前置节点存在
        prev = center->prev;
        if (center->prev->prev)  前置的前置存在
            prev_prev = center->prev->prev;
    }

    if (center->next) {  下一个节点存在
        next = center->next;
        if (center->next->next)
            next_next = center->next->next;
    }

    /* Try to merge prev_prev and prev */ 尝试合并前置的前置和 前置
    if (_quicklistNodeAllowMerge(prev, prev_prev, fill)) {
        _quicklistZiplistMerge(quicklist, prev_prev, prev); 合并前置的前置 和 前置节点,不需要返回值,因为定位的节点center还在
        prev_prev = prev = NULL; /* they could have moved, invalidate them. */ 传入的参数可能不再有效,置为空
    }

    /* Try to merge next and next_next */ 尝试合并下一个和 下一个的下一个节点
    if (_quicklistNodeAllowMerge(next, next_next, fill)) {
        _quicklistZiplistMerge(quicklist, next, next_next);   不需要返回值,因为定位的节点center还在
        next = next_next = NULL; /* they could have moved, invalidate them. */
    }

    /* Try to merge center node and previous node */ 尝试合并当前节点和 其前置节点
    if (_quicklistNodeAllowMerge(center, center->prev, fill)) {
        target = _quicklistZiplistMerge(quicklist, center->prev, center); 
        这里需要返回值是因为需要一个有效定位,之前一直是用center,但是这里center也会失效,需要重新确认一个定位
        center = NULL; /* center could have been deleted, invalidate it. */  当前节点可能被删除,置为无效
    } else {
        /* else, we didn't merge here, but target needs to be valid below. */
        不能合并,但是target在下面的操作中需要有效
        target = center;
    }

    /* Use result of center merge (or original) to merge with next node. */
    使用当前节点的合并或者原节点 去 合并下一个节点
    if (_quicklistNodeAllowMerge(target, target->next, fill)) {
        _quicklistZiplistMerge(quicklist, target, target->next); 
    }
}
*************************************************************************************************************
/* Split 'node' into two parts, parameterized by 'offset' and 'after'.
通过参数 offset 和  after  分割节点为两个部分
 * The 'after' argument controls which quicklistNode gets returned.
参数after 控制 哪个列表节点返回
 * If 'after'==1, returned node has elements after 'offset'.
如果after=1,返回 后一个节点, 在offset个元素之后的节点(分割开来的新节点)
 *                input node keeps elements up to 'offset', including 'offset'.
输入节点中包含元素直到offset位置,包含offset位置的节点
 * If 'after'==0, returned node has elements up to 'offset', including 'offset'.
 *                input node keeps elements after 'offset'.
如果after=0,返回 前面的节点,包含直到offset位置元素的节点, 包括offset位置节点的元素
                   输入节点包含offset之后的元素
(下面这段的意思和上面一模一样,不过下面这段用数学语言表示更加清晰,建议只用下面这段)
 * If 'after'==1, returned node will have elements _after_ 'offset'.
 *                The returned node will have elements [OFFSET+1, END].
 *                The input node keeps elements [0, OFFSET].
如果'after'==1,返回节点包含的元素 是 在位置 offset 之后的元素,从OFFSET+1 到 END,输入节点保持从0到OFFSET的元素

 * If 'after'==0, returned node will keep elements up to and including 'offset'.
 *                The returned node will have elements [0, OFFSET].
 *                The input node keeps elements [OFFSET+1, END].
如果after==0,返回节点 包含从从0到OFFSET的元素,输入节点包含从OFFSET+1 到 END的节点
 * The input node keeps all elements not taken by the returned node.
 *
 * Returns newly created node or NULL if split not possible. */
REDIS_STATIC quicklistNode *_quicklistSplitNode(quicklistNode *node, int offset,
                                                int after) {
    size_t zl_sz = node->sz; 压缩列表的长度

    quicklistNode *new_node = quicklistCreateNode();  创建新节点
    new_node->zl = zmalloc(zl_sz);  分配空间

    /* Copy original ziplist so we can split it */ 拷贝元素的压缩列表到新分配的空间,方便分割
    memcpy(new_node->zl, node->zl, zl_sz);

    /* -1 here means "continue deleting until the list ends" */ -1在这里的意思是 继续删除知道列表结束
    int orig_start = after ? offset + 1 : 0;  
    开始位置和after的值有关,当为1时,原始节点包含后面部分元素,为0时候,原始节点包含的是前面部分元素
    int orig_extent = after ? -1 : offset;
    int new_start = after ? 0 : offset; 新的返回节点刚好和原始节点相反
    int new_extent = after ? offset + 1 : -1;

    D("After %d (%d); ranges: [%d, %d], [%d, %d]", after, offset, orig_start,
      orig_extent, new_start, new_extent);

    node->zl = ziplistDeleteRange(node->zl, orig_start, orig_extent);删除原压缩列表的一个范围内的元素
    node->count = ziplistLen(node->zl); 重新设置节点元素个数
    quicklistNodeUpdateSz(node);更新节点的字节计数长度

    new_node->zl = ziplistDeleteRange(new_node->zl, new_start, new_extent);
    new_node->count = ziplistLen(new_node->zl);
    quicklistNodeUpdateSz(new_node);

    D("After split lengths: orig (%d), new (%d)", node->count, new_node->count);
    return new_node; 返回新的节点
}
*************************************************************************************************************
/* Insert a new entry before or after existing entry 'entry'.
在实体entry的前面或者后面插入一个新实体,
 * If after==1, the new value is inserted after 'entry', otherwise
 * the new value is inserted before 'entry'. */
如果after==1,那么新值插入到entry后面,否则新值插入到entry之前
REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry,
                                   void *value, const size_t sz, int after) {
    int full = 0, at_tail = 0, at_head = 0, full_next = 0, full_prev = 0;
    int fill = quicklist->fill; 填充因子
    quicklistNode *node = entry->node;
    quicklistNode *new_node = NULL;

    if (!node) {  实体没有引用的节点
        /* we have no reference node, so let's create only node in the list */
        没有引用的节点,让我们在列表中创建一个节点
        D("No node given!");
        new_node = quicklistCreateNode(); 创建新节点
        new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD); 创建一个新压缩列表,从头部插入一个元素
        __quicklistInsertNode(quicklist, NULL, new_node, after); 插入新节点
        new_node->count++; 节点元素加1
        quicklist->count++; 节点计数加1
        return;
    }

    /* Populate accounting flags for easier boolean checks later */
    填充记账标志 方便后面布尔检查
    if (!_quicklistNodeAllowInsert(node, fill, sz)) { 表示这个节点已经满了,不能在插入新元素,,设置标志
        D("Current node is full with count %d with requested fill %lu",
          node->count, fill);
        full = 1;
    }

    if (after && (entry->offset == node->count)) {  
    在当前节点后面插入 并且 偏移量是是当前节点的最后一个元素所在位置,那么需要插入下一个节点中
        D("At Tail of current ziplist");
        at_tail = 1;
        if (!_quicklistNodeAllowInsert(node->next, fill, sz)) { 当前节点的下一个节点满了,不能插入了,设置标志
            D("Next node is full too.");
            full_next = 1;
        }
    }

    if (!after && (entry->offset == 0)) {
    在当前节点前面插入 并且 当前的偏移量就是位于头部,那插入节点就是前一个节点了
        D("At Head");
        at_head = 1;
        if (!_quicklistNodeAllowInsert(node->prev, fill, sz)) { 当前节点的前一个节点已满,设置满的标志
            D("Prev node is full too.");
            full_prev = 1;
        }
    }

    /* Now determine where and how to insert the new element */ 现在来确定哪里和如何插入这个新元素
    if (!full && after) {  当前节点没有满,并且是后面插入
        D("Not full, inserting after current position.");
        quicklistDecompressNodeForUse(node);  对节点临时解压使用
        unsigned char *next = ziplistNext(node->zl, entry->zi);获取压缩列表当前元素的下一个元素位置
        if (next == NULL) {
            node->zl = ziplistPush(node->zl, value, sz, ZIPLIST_TAIL); 下一个为空,直接在尾巴插入一个元素
        } else {
            node->zl = ziplistInsert(node->zl, next, value, sz); 不为空,在下一个位置处插入新元素
        }
        node->count++; 节点元素计数加1
        quicklistNodeUpdateSz(node); 节点字节长度修改
        quicklistRecompressOnly(quicklist, node); 重新压缩
    } else if (!full && !after) { 当前节点没有满 并且在前面插入
        D("Not full, inserting before current position.");
        quicklistDecompressNodeForUse(node); 临时解压使用
        node->zl = ziplistInsert(node->zl, entry->zi, value, sz); 插入新元素
        node->count++;
        quicklistNodeUpdateSz(node);
        quicklistRecompressOnly(quicklist, node);
    } else if (full && at_tail && node->next && !full_next && after) {  
    当前节点满了 并且 插入的位置是尾巴位置  而且下一个节点存在  并且 下一个节点不满  并且 在后面插入
        /* If we are: at tail, next has free space, and inserting after:
         *   - insert entry at head of next node. */
         如果我们现在  在尾部  下一个节点有空间  在后面插入  那么插入实体元素到下一个节点的头部
        D("Full and tail, but next isn't full; inserting next node head");
        new_node = node->next;获取下一个节点
        quicklistDecompressNodeForUse(new_node); 临时解压新节点
        new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_HEAD); 将元素插入下一个接地那的压缩列表头部
        new_node->count++; 下一个节点的元素计数加1
        quicklistNodeUpdateSz(new_node);
        quicklistRecompressOnly(quicklist, new_node);
    } else if (full && at_head && node->prev && !full_prev && !after) {
当前节点满了 并且 插入的位置是头部位置   而且 前置节点存在  并且 前置节点不满  并且 在前面插入
        /* If we are: at head, previous has free space, and inserting before:
         *   - insert entry at tail of previous node. */
         如果我们现在 在头部  前置节点有空间 在前面插入, 在前置节点的尾部插入新实体元素
        D("Full and head, but prev isn't full, inserting prev node tail");
        new_node = node->prev;
        quicklistDecompressNodeForUse(new_node);
        new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_TAIL);在压缩列表尾部插入一个新元素
        new_node->count++;
        quicklistNodeUpdateSz(new_node);
        quicklistRecompressOnly(quicklist, new_node);
    } else if (full && ((at_tail && node->next && full_next && after) ||  当前节点已满  在尾部 下个节点存在但是已满  后面插入
                        (at_head && node->prev && full_prev && !after))) { 或者  在头部  前置节点存在但是已满  在前面插入
        /* If we are: full, and our prev/next is full, then:
         *   - create new node and attach to quicklist */
         如果我们现在  当前节点已满  同时我们的前置节点或者下一个节点也是满的,那么我们创建一个新节点添加到列表上去
        D("\tprovisioning new node...");
        new_node = quicklistCreateNode(); 创建新节点
        new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD); 创建新压缩列表,从头部插入一个新元素
        new_node->count++;
        quicklistNodeUpdateSz(new_node);
        __quicklistInsertNode(quicklist, node, new_node, after);
    } else if (full) {  如果当前节点已满 的剩余情况
(就是要插入的元素在中间位置,前置时不是最前,下一个时候也不是最后,需要插入到本节点列表中,看这个逻辑的情况,快排列表的元素是有序的)
        /* else, node is full we need to split it. */  当前节点已满,我们需要分割这个节点
        /* covers both after and !after cases */  同时覆盖 前后两种情况
        D("\tsplitting node...");
        quicklistDecompressNodeForUse(node);临时解压节点
        new_node = _quicklistSplitNode(node, entry->offset, after);
        new_node->zl = ziplistPush(new_node->zl, value, sz,
                                   after ? ZIPLIST_HEAD : ZIPLIST_TAIL); 根据表示在前面或者后面插入新元素
        new_node->count++;
        quicklistNodeUpdateSz(new_node);
        __quicklistInsertNode(quicklist, node, new_node, after);
        _quicklistMergeNodes(quicklist, node);
    }

    quicklist->count++; 快排列表的元素加1
}
*************************************************************************************************************
在指定实体元素前面插入
void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *entry,
                           void *value, const size_t sz) {
    _quicklistInsert(quicklist, entry, value, sz, 0);
}
在执行实体元素后面插入
void quicklistInsertAfter(quicklist *quicklist, quicklistEntry *entry,
                          void *value, const size_t sz) {
    _quicklistInsert(quicklist, entry, value, sz, 1);
}
*************************************************************************************************************
/* Delete a range of elements from the quicklist.
从快排列表中删除一个范围内的元素
 * elements may span across multiple quicklistNodes, so we
 * have to be careful about tracking where we start and end.
元素可能跨越多个快排列表的节点,所以我们必须小心跟踪 开始位置和结束位置
 * Returns 1 if entries were deleted, 0 if nothing was deleted. */
返回1如果 删除实体元素成功,0 如果没有元素被删除
int quicklistDelRange(quicklist *quicklist, const long start,
                      const long count) {
    if (count <= 0) 删除区间小于0,直接返回
        return 0;

    unsigned long extent = count; /* range is inclusive of start position */ 范围包括了起始位置

    if (start >= 0 && extent > (quicklist->count - start)) { 起始位置从0开始 而且 范围超过了后面的元素个数
        /* if requesting delete more elements than exist, limit to list size. */
        如果请求删除的元素超过了剩下的幻速,限制大小到剩下的所有元素
        extent = quicklist->count - start; 将返回缩小到剩下的所有元素
    } else if (start < 0 && extent > (unsigned long)(-start)) { 反向的情况,如果开始位置小于0,删除范围超过了剩下的元素
        /* else, if at negative offset, limit max size to rest of list. */
        如果是负偏移量,同样现在最大的大小为列表剩余元素
        extent = -start; /* c.f. LREM -29 29; just delete until end. */ 删除范围缩小到剩下的所有元素
    }

    quicklistEntry entry;
    if (!quicklistIndex(quicklist, start, &entry)) 定位起始元素 找不到就退出
        return 0;

    D("Quicklist delete request for start %ld, count %ld, extent: %ld", start,
      count, extent);
    quicklistNode *node = entry.node; 找到了元素所在的节点

    /* iterate over next nodes until everything is deleted. */ 迭代直到要删除的所有元素删除完为止
    while (extent) {
        quicklistNode *next = node->next;
        获取当前节点的下一个节点(万一当前节点不够删,需要删下一个节点,提前做好准备)

        unsigned long del;
        int delete_entire_node = 0;
        if (entry.offset == 0 && extent >= node->count) { 
        如果偏移量为0,表示开始位置 而且剩余删除范围超过了,那么可以删除整个节点
            /* If we are deleting more than the count of this node, we
             * can just delete the entire node without ziplist math. */
             如果我们正在删除的范围超过了当前节点的元素个数,我们可以直接删除整个节点而不用匹配压缩列表
            delete_entire_node = 1; 
            del = node->count;
        } else if (entry.offset >= 0 && extent >= node->count) { 如果偏移量大于0  同时 删除范围大于当前节点的所有元素
            /* If deleting more nodes after this one, calculate delete based
             * on size of current node. */
             如果在这个元素之后需要删除更多的元素,通过基于当前节点的元素计算累计删除个数
            del = node->count - entry.offset;
        } else if (entry.offset < 0) { 
            /* If offset is negative, we are in the first run of this loop
             * and we are deleting the entire range
             * from this start offset to end of list.  Since the Negative
             * offset is the number of elements until the tail of the list,
             * just use it directly as the deletion count. */
如果偏移量是负数,那么必定是第一次进入这个循环,我们需要删除从这偏移量开始到列表结束。
因为负的偏移量是 当前元素到列表结尾的元素个数, 直接使用这个值作为删除值
            del = -entry.offset;

            /* If the positive offset is greater than the remaining extent,
             * we only delete the remaining extent, not the entire offset.
             */如果正的偏移量大于留下来的删除范围,我们只删除留下来的删除范围个元素,不是整个偏移量
            if (del > extent)
                del = extent;
        } else {
            /* else, we are deleting less than the extent of this node, so
             * use extent directly. */
             如果我们删除的元素个数小于当前节点的个数,我们使用删除范围的值就可以了
            del = extent;
        }

        D("[%ld]: asking to del: %ld because offset: %d; (ENTIRE NODE: %d), "
          "node count: %u",
          extent, del, entry.offset, delete_entire_node, node->count);

        if (delete_entire_node) { 可以删除整个节点
            __quicklistDelNode(quicklist, node);
        } else {
            quicklistDecompressNodeForUse(node); 临时解压缩
            node->zl = ziplistDeleteRange(node->zl, entry.offset, del); 删除压缩列表范围内的待删除元素
            quicklistNodeUpdateSz(node);更新压缩列表的字节大小
            node->count -= del; 节点元素个数减去删除元素个数
            quicklist->count -= del; 快排列表元素个数减去 删除元素个数
            quicklistDeleteIfEmpty(quicklist, node); 删除空节点
            if (node)
                quicklistRecompressOnly(quicklist, node); 节点非空,重新压缩回去
        }

        extent -= del; 删除范围缩小

        node = next; 指向下个节点

        entry.offset = 0; 偏移量指向节点开始处
    }
    return 1;
}
*************************************************************************************************************
/* Passthrough to ziplistCompare() */ 调用函数ziplistCompare
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len) {
    return ziplistCompare(p1, p2, p2_len); 比较两个压缩列表是否相等
}
*************************************************************************************************************
/* Returns a quicklist iterator 'iter'. After the initialization every
 * call to quicklistNext() will return the next element of the quicklist. */
返回一个快排列表的迭代器iter。经过初始化,每一个调用函数quicklistNext将会返回快排列表下一个元素
quicklistIter *quicklistGetIterator(const quicklist *quicklist, int direction) {
    quicklistIter *iter;

    iter = zmalloc(sizeof(*iter)); 给迭代器分配空间

    if (direction == AL_START_HEAD) { 从头开始
        iter->current = quicklist->head;
        iter->offset = 0;
    } else if (direction == AL_START_TAIL) {从尾巴开始
        iter->current = quicklist->tail;
        iter->offset = -1;
    }

    iter->direction = direction; 迭代方向
    iter->quicklist = quicklist; 迭代列表

    iter->zi = NULL; 指向当前节点的数据(压缩列表)

    return iter;
}
*************************************************************************************************************
/* Initialize an iterator at a specific offset 'idx' and make the iterator
 * return nodes in 'direction' direction. */
在一个特定的偏移量idx初始化迭代器,使得迭代器返回指定方向上的节点
quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist,
                                         const int direction,
                                         const long long idx) {
    quicklistEntry entry;

    if (quicklistIndex(quicklist, idx, &entry)) { 如果找到给定偏移量位置的元素
        quicklistIter *base = quicklistGetIterator(quicklist, direction); 初始化迭代器
        base->zi = NULL;
        base->current = entry.node;
        base->offset = entry.offset;
        return base;
    } else {
        return NULL;
    }
}
*************************************************************************************************************
/* Release iterator.
 * If we still have a valid current node, then re-encode current node. */
释放迭代器。如果我们还拥有一个有效的当前节点,重新压缩当前节点
void quicklistReleaseIterator(quicklistIter *iter) {
    if (iter->current)
        quicklistCompress(iter->quicklist, iter->current); 当前节点有效,重新压缩

    zfree(iter); 释放迭代器
}
*************************************************************************************************************
/* Get next element in iterator.
在迭代器中获取下一个元素
 * Note: You must NOT insert into the list while iterating over it.
 * You *may* delete from the list while iterating using the
 * quicklistDelEntry() function.
 * If you insert into the quicklist while iterating, you should
 * re-create the iterator after your addition.
注意:你不能做迭代的时候,在列表中插入元素,但是你可以在迭代中使用函数quicklistDelEntry删除元素。
如果你迭代中插入元素到快排列表,你应该在添加元素后重新创建迭代器
 * iter = quicklistGetIterator(quicklist,<direction>);
 * quicklistEntry entry;
 * while (quicklistNext(iter, &entry)) {
 *     if (entry.value)
 *          [[ use entry.value with entry.sz ]]
 *     else
 *          [[ use entry.longval ]]
 * }
 *
 * Populates 'entry' with values for this iteration.
 用迭代的值填充entry
 * Returns 0 when iteration is complete or if iteration not possible.
返回0如果迭代结束或者不能再迭代
 * If return value is 0, the contents of 'entry' are not valid.
如果返回0,那么entry中的内容不再有效
 */
int quicklistNext(quicklistIter *iter, quicklistEntry *entry) {
    initEntry(entry);  初始化实体

    if (!iter) { 迭代器为空
        D("Returning because no iter!");
        return 0;
    }

    entry->quicklist = iter->quicklist;
    entry->node = iter->current;

    if (!iter->current) { 当前节点为空
        D("Returning because current node is NULL")
        return 0;
    }

    unsigned char *(*nextFn)(unsigned char *, unsigned char *) = NULL; 定义函数指针
    int offset_update = 0;

    if (!iter->zi) { 如果迭代器指向的压缩列表元素为空
        /* If !zi, use current index. */ 如果指向的压缩列表为空,使用当前节点的
        quicklistDecompressNodeForUse(iter->current);解压当前节点
        iter->zi = ziplistIndex(iter->current->zl, iter->offset); 指向当前节点压缩列表偏移量所在位置
    } else {
        /* else, use existing iterator offset and get prev/next as necessary. */
        使用迭代器中的偏移量  根据方向获取前一个或者下一个元素
        if (iter->direction == AL_START_HEAD) { 
            nextFn = ziplistNext; 获取下一个元素函数
            offset_update = 1;
        } else if (iter->direction == AL_START_TAIL) {
            nextFn = ziplistPrev; 获取前一个元素的函数
            offset_update = -1;
        }
        iter->zi = nextFn(iter->current->zl, iter->zi); 获取 前一个或者后一个元素
        iter->offset += offset_update; 指向下一个偏移量
    }

    entry->zi = iter->zi;
    entry->offset = iter->offset;

    if (iter->zi) {
        /* Populate value from existing ziplist position */从压缩列表存在的位置中填充值
        ziplistGet(entry->zi, &entry->value, &entry->sz, &entry->longval);
        return 1;
    } else {
        /* We ran out of ziplist entries.
         * Pick next node, update offset, then re-run retrieval. */
         我们跑出了压缩列表的范围,获取下一个节点,更新偏移量,然后重新运行检索
        quicklistCompress(iter->quicklist, iter->current);压缩当前节点
        if (iter->direction == AL_START_HEAD) {
            /* Forward traversal */前向遍历
            D("Jumping to start of next node");
            iter->current = iter->current->next;
            iter->offset = 0;
        } else if (iter->direction == AL_START_TAIL) {
            /* Reverse traversal */反向遍历
            D("Jumping to end of previous node");
            iter->current = iter->current->prev;
            iter->offset = -1;
        }
        iter->zi = NULL; 指向压缩列表节点元素为空
        return quicklistNext(iter, entry); 迭代获取下一个元素的值
    }
}
*************************************************************************************************************
/* Duplicate the quicklist.
 * On success a copy of the original quicklist is returned.
复制快排列表 如果成功返回一个原始快排列表的拷贝
 * The original quicklist both on success or error is never modified.
原始列表不受这个函数的影响,无论失败和成功不受任何影响
 * Returns newly allocated quicklist. */
返回一个新分配的快排列表
quicklist *quicklistDup(quicklist *orig) {
    quicklist *copy;

    copy = quicklistNew(orig->fill, orig->compress);创建新列表

    for (quicklistNode *current = orig->head; current;
         current = current->next) { 遍历整个快排列表
        quicklistNode *node = quicklistCreateNode(); 创建新的快排列表节点

        if (current->encoding == QUICKLIST_NODE_ENCODING_LZF) { 如果是压缩模式
            quicklistLZF *lzf = (quicklistLZF *)current->zl; 需要创建一个压缩的结构体
            size_t lzf_sz = sizeof(*lzf) + lzf->sz;
            node->zl = zmalloc(lzf_sz);
            memcpy(node->zl, current->zl, lzf_sz);
        } else if (current->encoding == QUICKLIST_NODE_ENCODING_RAW) { 非压缩模式
            node->zl = zmalloc(current->sz);  直接分配空间拷贝内容即可
            memcpy(node->zl, current->zl, current->sz);
        }

        node->count = current->count; 当前节点的元素个数
        copy->count += node->count;  快排列表的总的元素个数
        node->sz = current->sz; 当前节点的字节数
        node->encoding = current->encoding; 当前节点的编码

        _quicklistInsertNodeAfter(copy, copy->tail, node); 在快排列表尾部插入一个新节点
    }

    /* copy->count must equal orig->count here */ 拷贝的快排列表的总元素个数 应该 和 原始的快排列表相同
    return copy;
}
*************************************************************************************************************
/* Populate 'entry' with the element at the specified zero-based index
 * where 0 is the head, 1 is the element next to head
 * and so on. Negative integers are used in order to count
 * from the tail, -1 is the last element, -2 the penultimate
 * and so on. If the index is out of range 0 is returned.
用特定的基于0开始的索引所在的元素来填充entry
0就是头部元素,1就是紧挨着头部的第二个元素,依次如下。
负整数表示从尾部开始计数,-1表示最后一个元素,-2就是倒数第二个元素,依次如下。
如果索引超出了列表的范围,返回0。返回1如果元素被找到,返回0如果元素找不到
 * Returns 1 if element found
 * Returns 0 if element not found */
int quicklistIndex(const quicklist *quicklist, const long long idx,
                   quicklistEntry *entry) {
    quicklistNode *n;
    unsigned long long accum = 0;
    unsigned long long index;
    int forward = idx < 0 ? 0 : 1; /* < 0 -> reverse, 0+ -> forward */ 
    确定方向,小于0反向,大于等于0正向

    initEntry(entry);初始化实体
    entry->quicklist = quicklist; 赋值快排列表

    if (!forward) { 非正向
        index = (-idx) - 1; 从尾部开始计数,转成正向计数
        n = quicklist->tail;
    } else {
        index = idx;
        n = quicklist->head;
    }

    if (index >= quicklist->count) 索引范围超过了快排列表的总元素个数,直接返回
        return 0;

    while (likely(n)) { 指向的节点不为空
        if ((accum + n->count) > index) { 计算当前总的元素数量是否超过了索引,超过就停止,因为在这个范围内
            break;
        } else { 没有超过,继续累加计数从下个节点寻找
            D("Skipping over (%p) %u at accum %lld", (void *)n, n->count,
              accum);
            accum += n->count;
            n = forward ? n->next : n->prev; 根据方向向前或者向后寻找
        }
    }

    if (!n)  如果节点为空,返回失败
        return 0;

    D("Found node: %p at accum %llu, idx %llu, sub+ %llu, sub- %llu", (void *)n,
      accum, index, index - accum, (-index) - 1 + accum);

    entry->node = n; 节点非空的情况下,意味着找到了索引所在位置
    if (forward) { 如果是前向查找,正常的从头到尾的偏移量
        /* forward = normal head-to-tail offset. */ 
        entry->offset = index - accum;
    } else { 如果是反向查找 需要负的从尾巴到头的偏移量,所以需要如果最初index是负的,需要回到最初的负值
        /* reverse = need negative offset for tail-to-head, so undo
         * the result of the original if (index < 0) above. */
        entry->offset = (-index) - 1 + accum;  这里的结果是个负值
    }

    quicklistDecompressNodeForUse(entry->node); 解压找到的节点
    entry->zi = ziplistIndex(entry->node->zl, entry->offset);根据偏移量在压缩列表中选择元素
    ziplistGet(entry->zi, &entry->value, &entry->sz, &entry->longval);获取元素的值
    /* The caller will use our result, so we don't re-compress here. 调用者将使用我们返回的结果,所以我们在这里不压缩
     * The caller can recompress or delete the node as needed. */ 调用者可以根据需要压缩或者删除这个返回的节点
    return 1;
}
*************************************************************************************************************
/* Rotate quicklist by moving the tail element to the head. */
旋转快排列表的尾部元素到头部
void quicklistRotate(quicklist *quicklist) {
    if (quicklist->count <= 1) 只有小于等于1个节点,不需要做这个旋转操作
        return;

    /* First, get the tail entry */ 首先获取尾部节点
    unsigned char *p = ziplistIndex(quicklist->tail->zl, -1); 获取压缩列表尾部节点的最后一个元素
    unsigned char *value;
    long long longval;
    unsigned int sz;
    char longstr[32] = {0};
    ziplistGet(p, &value, &sz, &longval); 获取该实体的值

    /* If value found is NULL, then ziplistGet populated longval instead */ 如果值为空,那么ziplistGet填充的是longval值
    if (!value) { 如果值为空,那么不是字符串,是整型
        /* Write the longval as a string so we can re-add it */ 将整型转化为字符串,这样我们可以重新赋值
        sz = ll2string(longstr, sizeof(longstr), longval);
        value = (unsigned char *)longstr;
    }

    /* Add tail entry to head (must happen before tail is deleted). */ 必须在尾部元素被删除前(因为删除元素会改变压缩列表)
    quicklistPushHead(quicklist, value, sz);

    /* If quicklist has only one node, the head ziplist is also the
     * tail ziplist and PushHead() could have reallocated our single ziplist,
     * which would make our pre-existing 'p' unusable. */
如果快排列表只有一个节点,那么头部的压缩列表就是尾部的压缩列表,调用PushHead函数可能导致我们的单压缩列表被重新分配空间
    if (quicklist->len == 1) {
        p = ziplistIndex(quicklist->tail->zl, -1);尾部元素店址有变化
    }

    /* Remove tail entry. */ 移除尾部元素
    quicklistDelIndex(quicklist, quicklist->tail, &p);
}
*************************************************************************************************************
/* pop from quicklist and return result in 'data' ptr.  Value of 'data'
 * is the return value of 'saver' function pointer if the data is NOT a number.
从快排列表弹出一个元素,返回结果在指针data中。data的值是函数指针saver返回的值,如果data是不是一个数字,
 * If the quicklist element is a long long, then the return value is returned in 'sval'.
 如果弹出的值是一个长整型,那么返回的值就在sval中
 * Return value of 0 means no elements available. 返回0如果没有元素
 * Return value of 1 means check 'data' and 'sval' for values.返回1意味着检查data和sval来获取值
 * If 'data' is set, use 'data' and 'sz'.  Otherwise, use 'sval'. */ 
 如果data被设置(返回字符串),那么使用data和sz,否则使用sval
int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
                       unsigned int *sz, long long *sval,
                       void *(*saver)(unsigned char *data, unsigned int sz)) {
    unsigned char *p;
    unsigned char *vstr;
    unsigned int vlen;
    long long vlong;
    int pos = (where == QUICKLIST_HEAD) ? 0 : -1;弹出的位置是头部还是尾部

    if (quicklist->count == 0) 没有元素可以弹出
        return 0;
清除原来数据
    if (data) 
        *data = NULL;
    if (sz)
        *sz = 0;
    if (sval)
        *sval = -123456789;

    quicklistNode *node;
    if (where == QUICKLIST_HEAD && quicklist->head) { 如果弹出头部元素 并且头节点非空
        node = quicklist->head; 获取头节点
    } else if (where == QUICKLIST_TAIL && quicklist->tail) { 如果弹出尾部元素,并且尾部节点非空
        node = quicklist->tail;
    } else {
        return 0;
    }

    p = ziplistIndex(node->zl, pos); 从节点的压缩列表中根据位置获取元素
    if (ziplistGet(p, &vstr, &vlen, &vlong)) { 获取元素的值
        if (vstr) {
            if (data) 字符串类型
                *data = saver(vstr, vlen); 通过函数指针获取具体的字符串
            if (sz)
                *sz = vlen;
        } else { 整型
            if (data)
                *data = NULL;
            if (sval)
                *sval = vlong;
        }
        quicklistDelIndex(quicklist, node, &p);删除快排列表指定位置的元素
        return 1;
    }
    return 0;
}
*************************************************************************************************************
/* Return a malloc'd copy of data passed in */
返回一个传入数据的拷贝值
REDIS_STATIC void *_quicklistSaver(unsigned char *data, unsigned int sz) {
    unsigned char *vstr;
    if (data) { 传入数据非空
        vstr = zmalloc(sz); 分配空间
        memcpy(vstr, data, sz); 拷贝数据
        return vstr;
    }
    return NULL;
}
*************************************************************************************************************
/* Default pop function
默认的弹出函数
 * Returns malloc'd value from quicklist */
返回从快排列表分配的值
int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
                 unsigned int *sz, long long *slong) {
    unsigned char *vstr;
    unsigned int vlen;
    long long vlong;
    if (quicklist->count == 0) 无元素可弹
        return 0;
    int ret = quicklistPopCustom(quicklist, where, &vstr, &vlen, &vlong,
                                 _quicklistSaver);  从头部或者尾部弹出元素
    if (data) 字符串
        *data = vstr;
    if (slong) 整型
        *slong = vlong;
    if (sz)
        *sz = vlen;
    return ret;
}
*************************************************************************************************************
/* Wrapper to allow argument-based switching between HEAD/TAIL pop */ pop笔误
封装基于参数切换的头部或者尾部压入函数
void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
                   int where) {
    if (where == QUICKLIST_HEAD) { 从头部压入元素
        quicklistPushHead(quicklist, value, sz);
    } else if (where == QUICKLIST_TAIL) { 从尾部压入元素
        quicklistPushTail(quicklist, value, sz);
    }
}
*************************************************************************************************************
/* Create or update a bookmark in the list which will be updated to the next node
 * automatically when the one referenced gets deleted.
 * Returns 1 on success (creation of new bookmark or override of an existing one).
 * Returns 0 on failure (reached the maximum supported number of bookmarks).
 * NOTE: use short simple names, so that string compare on find is quick.
 * NOTE: bookmakrk creation may re-allocate the quicklist, so the input pointer
         may change and it's the caller responsibilty to update the reference.
 */
在列表中创建或者更新一个书签,当引用的书签节点被删除时,会自动更新到下一个节点
返回1如果成功(创建一个新的书签节点或者覆盖一个已经存在的节点)
返回0如果失败(到达支持的最大书签个数)
注意:使用简称,这样可以使得通过字符串对比查找更快
注意:书签的创建可能需要重新分配列表的空间,这种情况会导致输入的指针改变,
这个是调用者的责任区更新相关的引用
int quicklistBookmarkCreate(quicklist **ql_ref, const char *name, quicklistNode *node) {
    quicklist *ql = *ql_ref;
    if (ql->bookmark_count >= QL_MAX_BM)已经达到最大值
        return 0;
    quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name); 通过名字查找元素
    if (bm) { 找到就返回
        bm->node = node;
        return 1;
    }
    没有找到需要新建
    ql = zrealloc(ql, sizeof(quicklist) + (ql->bookmark_count+1) * sizeof(quicklistBookmark)); 
    *ql_ref = ql; 重新定位,因为重新分配空间地址可能会变
    ql->bookmarks[ql->bookmark_count].node = node; 新的节点
    ql->bookmarks[ql->bookmark_count].name = zstrdup(name);深拷贝字符串
    ql->bookmark_count++;书签计数加1
    return 1;
}
*************************************************************************************************************
/* Find the quicklist node referenced by a named bookmark.
 * When the bookmarked node is deleted the bookmark is updated to the next node,
 * and if that's the last node, the bookmark is deleted (so find returns NULL). */
通过命名的书签查找引用的列表的节点 
当书签标记的节点被删除,那么书签就被更新为下一个节点
并且如果那是最后一个节点,那么书签就被删除了(所以查找返回空)
quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name) {
    quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);通过名字查找书签
    if (!bm) return NULL;
    return bm->node;返回书签指向的节点
}
*************************************************************************************************************
/* Delete a named bookmark.
 * returns 0 if bookmark was not found, and 1 if deleted.
 * Note that the bookmark memory is not freed yet, and is kept for future use. */
删除一个命名的书签。
返回0如果书签没有被找到,返回1如果被删除了。
注意 这个书签的内存还没有被释放,用来保持给将来使用
int quicklistBookmarkDelete(quicklist *ql, const char *name) {
    quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
    if (!bm) 为空
        return 0;
    _quicklistBookmarkDelete(ql, bm); 删除书签
    return 1;
}
*************************************************************************************************************
通过名字查找书签
quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name) {
    unsigned i;
    for (i=0; i<ql->bookmark_count; i++) { 对书签数组做循环查找
        if (!strcmp(ql->bookmarks[i].name, name)) { 对比书签名字
            return &ql->bookmarks[i];
        }
    }
    return NULL;
}
*************************************************************************************************************
通过节点查找书签
quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node) {
    unsigned i;
    for (i=0; i<ql->bookmark_count; i++) { 遍历书签数组
        if (ql->bookmarks[i].node == node) { 对比节点引用
            return &ql->bookmarks[i];
        }
    }
    return NULL;
}
*************************************************************************************************************
删除书签数组节点
void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm) {
    int index = bm - ql->bookmarks; 找到书签所在数组的索引
    zfree(bm->name);  释放书签名字的空间
    ql->bookmark_count--; 书签计数减1
    memmove(bm, bm+1, (ql->bookmark_count - index)* sizeof(*bm)); 移动数组,删除指定书签
    /* NOTE: We do not shrink (realloc) the quicklist yet (to avoid resonance,
     * it may be re-used later (a call to realloc may NOP). */
     注意我们这里并没有减少列表的内存(为了避免反复,减少又增加这样的情况)
     它可能后续会被重新使用(这种情不需要调用realloc分配内存)
}
*************************************************************************************************************
清除书签数组
void quicklistBookmarksClear(quicklist *ql) {
    while (ql->bookmark_count) 遍历书签数组
        zfree(ql->bookmarks[--ql->bookmark_count].name); 释放每一个数组的内容
    /* NOTE: We do not shrink (realloc) the quick list. main use case for this
     * function is just before releasing the allocation. */
     注意:我们没有减少列表的内存。这个函数的主要作用是在释放前清理数据
}
********************************************************************************************

 

上一篇:redis6.0.5之quicklist阅读笔记1--定义


下一篇:面试官:你看过Redis数据结构底层实现吗?