zoukankan      html  css  js  c++  java
  • redis 5.0.2 源码阅读——快速列表quicklist

    一、quicklist简介

      为什么说quicklist是“二合一”呢?如果你看过STL中的deque的实现,就会知道deque是由一个map中控器和一个数组组成的数据结构,它既具有链表头尾插入便捷的优点,又有数组连续内存存储,支持下标访问的优点。Redis中是采用sdlist和ziplist来实现quicklist的,其中sdlist充当map中控器的作用,ziplist充当占用连续内存空间数组的作用。quicklist本身是一个双向无环链表,它的每一个节点都是一个ziplist。为什么这么设计呢?

    • 双向链表在插入节点上复杂度很低,但它的内存开销很大,每个节点的地址不连续,容易产生内存碎片。
    • ziplist是存储在一段连续的内存上,存储效率高,但是它不利于修改操作,插入和删除数都很麻烦,复杂度高,而且其需要频繁的申请释放内存,特别是ziplist中数据较多的情况下,搬移内存数据太费时!

    Redis综合了双向链表和ziplist的优点,设计了quicklist这个数据结构,使它作为list键的底层实现。接下来,就要考虑每一个ziplist中存放的元素个数。

    • 如果每一个ziplist中的元素个数过少,内存碎片就会增多。可以按照极端情况双向链表来考虑。
    • 如果每一个ziplist中的元素个数过多,那么ziplist分配大块连续内存空间的难度就增大,同样会影响效率。

    Redis的配置文件中,给出了每个ziplist中的元素个数设定,考虑使用场景需求,我们可以选择不同的元素个数。该参数设置格式如下:

    1 list-max-ziplist-size -2

    当数字为负数,表示以下含义:

    • -1 每个quicklistNode节点的ziplist字节大小不能超过4kb。(建议)
    • -2 每个quicklistNode节点的ziplist字节大小不能超过8kb。(默认配置)
    • -3 每个quicklistNode节点的ziplist字节大小不能超过16kb。(一般不建议)
    • -4 每个quicklistNode节点的ziplist字节大小不能超过32kb。(不建议)
    • -5 每个quicklistNode节点的ziplist字节大小不能超过64kb。(正常工作量不建议)

    当数字为正数,表示:ziplist结构所最多包含的entry个数。最大值为 2^15。

      另外,在quicklist的源码中提到了一个LZF的压缩算法,该算法用于对quicklist的节点进行压缩操作。list的设计目的是能够存放很长的数据列表,当列表很长时,必然会占用很高的内存空间,且list中最容易访问的是两端的数据,中间的数据访问率较低,于是就可以从这个出发点来进一步节省内存用于其他操作。Redis提供了一下的配置参数,用于表示中间节点是否压缩。

    1 list-compress-depth 0

    compress成员对应的配置:list-compress-depth 0
    后面的数字有以下含义:

    • 0 表示不压缩。(默认)
    • 1 表示quicklist列表的两端各有1个节点不压缩,中间的节点压缩。
    • 2 表示quicklist列表的两端各有2个节点不压缩,中间的节点压缩。
    • 3 表示quicklist列表的两端各有3个节点不压缩,中间的节点压缩。
    • 以此类推,最大为 216216。

    通过列表键查看一下:

    1 127.0.0.1:6379> RPUSH list 1 2 5 1000
    2 "redis" "quicklist"(integer) 
    3 127.0.0.1:6379> OBJECT ENCODING list
    4 "quicklist"

    quicklist结构在quicklist.c中的解释为A doubly linked list of ziplists意思为一个由ziplist组成的双向链表。

    首先回忆下压缩列表的特点:

    • 压缩列表ziplist结构本身就是一个连续的内存块,由表头、若干个entry节点和压缩列表尾部标识符zlend组成,通过一系列编码规则,提高内存的利用率,使用于存储整数和短字符串。
    • 压缩列表ziplist结构的缺点是:每次插入或删除一个元素时,都需要进行频繁的调用realloc()函数进行内存的扩展或减小,然后进行数据”搬移”,甚至可能引发连锁更新,造成严重效率的损失。

    接下来介绍quicklist与ziplist的关系:

      之前提到,quicklist是由ziplist组成的双向链表,链表中的每一个节点都以压缩列表ziplist的结构保存着数据,而ziplist有多个entry节点,保存着数据。相当与一个quicklist节点保存的是一片数据,而不再是一个数据。

    例如:一个quicklist有4个quicklist节点,每个节点都保存着1个ziplist结构,每个ziplist的大小不超过8kb,ziplist的entry节点中的value成员保存着数据。

    根据以上描述,总结出一下quicklist的特点:

    • quicklist宏观上是一个双向链表,因此,它具有一个双向链表的有点,进行插入或删除操作时非常方便,虽然复杂度为O(n),但是不需要内存的复制,提高了效率,而且访问两端元素复杂度为O(1)。
    • quicklist微观上是一片片entry节点,每一片entry节点内存连续且顺序存储,可以通过二分查找以 log2(n)log2(n) 的复杂度进行定位。

    二、quicklist的结构实现

    quicklist有关的数据结构定义在quicklist.h中。

    2.1 quicklist表头结构

     1 typedef struct quicklist {
     2     //指向头部(最左边)quicklist节点的指针
     3     quicklistNode *head;
     4 
     5     //指向尾部(最右边)quicklist节点的指针
     6     quicklistNode *tail;
     7 
     8     //ziplist中的entry节点计数器
     9     unsigned long count;        /* total count of all entries in all ziplists */
    10 
    11     //quicklist的quicklistNode节点计数器
    12     unsigned int len;           /* number of quicklistNodes */
    13 
    14     //保存ziplist的大小,配置文件设定,占16bits
    15     int fill : 16;              /* fill factor for individual nodes */
    16 
    17     //保存压缩程度值,配置文件设定,占16bits,0表示不压缩
    18     unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
    19 } quicklist;

    在quicklist表头结构中,有两个成员是fill和compress,其中” : “是位域运算符,表示fill占int类型32位中的16位,compress也占16位。fill和compress的配置文件是redis.conf。

    2.2 quicklist节点结构

     1 typedef struct quicklistNode {
     2     struct quicklistNode *prev;     //前驱节点指针
     3     struct quicklistNode *next;     //后继节点指针
     4 
     5     //不设置压缩数据参数recompress时指向一个ziplist结构
     6     //设置压缩数据参数recompress指向quicklistLZF结构
     7     unsigned char *zl;
     8 
     9     //压缩列表ziplist的总长度
    10     unsigned int sz;                  /* ziplist size in bytes */
    11 
    12     //ziplist中包的节点数,占16 bits长度
    13     unsigned int count : 16;          /* count of items in ziplist */
    14 
    15     //表示是否采用了LZF压缩算法压缩quicklist节点,1表示压缩过,2表示没压缩,占2 bits长度
    16     unsigned int encoding : 2;        /* RAW==1 or LZF==2 */
    17 
    18     //表示一个quicklistNode节点是否采用ziplist结构保存数据,2表示压缩了,1表示没压缩,默认是2,占2bits长度
    19     unsigned int container : 2;       /* NONE==1 or ZIPLIST==2 */
    20 
    21     //标记quicklist节点的ziplist之前是否被解压缩过,占1bit长度
    22     //如果recompress为1,则等待被再次压缩
    23     unsigned int recompress : 1; /* was this node previous compressed? */
    24 
    25     //测试时使用
    26     unsigned int attempted_compress : 1; /* node can't compress; too small */
    27 
    28     //额外扩展位,占10bits长度
    29     unsigned int extra : 10; /* more bits to steal for future usage */
    30 } quicklistNode;

    2.3 压缩过的ziplist结构—quicklistLZF

    当指定使用lzf压缩算法压缩ziplist的entry节点时,quicklistNode结构的zl成员指向quicklistLZF结构

    1 typedef struct quicklistLZF {
    2     //表示被LZF算法压缩后的ziplist的大小
    3     unsigned int sz; /* LZF size in bytes*/
    4 
    5     //保存压缩后的ziplist的数组,柔性数组
    6     char compressed[];
    7 } quicklistLZF;

    2.4 管理ziplist信息的结构quicklistEntry

    和压缩列表一样,entry结构在储存时是一连串的内存块,需要将其每个entry节点的信息读取到管理该信息的结构体中,以便操作。在quicklist中定义了自己的结构。

     1 //管理quicklist中quicklistNode节点中ziplist信息的结构
     2 typedef struct quicklistEntry {
     3     const quicklist *quicklist;   //指向所属的quicklist的指针
     4     quicklistNode *node;          //指向所属的quicklistNode节点的指针
     5     unsigned char *zi;            //指向当前ziplist结构的指针
     6     unsigned char *value;         //指向当前ziplist结构的字符串vlaue成员
     7     long long longval;            //指向当前ziplist结构的整数value成员
     8     unsigned int sz;              //保存当前ziplist结构的字节数大小
     9     int offset;                   //保存相对ziplist的偏移量
    10 } quicklistEntry;

    基于以上结构信息,我们可以得出一个quicklist结构,在空间中的大致可能的样子:

     2.5 迭代器结构实现

    在redis的quicklist结构中,实现了自己的迭代器,用于遍历节点。

    1 //quicklist的迭代器结构
    2 typedef struct quicklistIter {
    3     const quicklist *quicklist;   //指向所属的quicklist的指针
    4     quicklistNode *current;       //指向当前迭代的quicklist节点的指针
    5     unsigned char *zi;            //指向当前quicklist节点中迭代的ziplist
    6     long offset;                  //当前ziplist结构中的偏移量      /* offset in current ziplist */
    7     int direction;                //迭代方向
    8 } quicklistIter;

    三、quicklist的部分操作源码注释

    quicklist.c和quicklist.h文件的注释:redis 源码注释

    3.1 插入一个entry节点

    quicklist的插入:以一个已存在的entry前或后插入一个entry节点,非常的复杂,因为情况非常多。

    • 当前quicklistNode节点的ziplist可以插入。
      • 插入在已存在的entry前
      • 插入在已存在的entry后
    • 如果当前quicklistNode节点的ziplist由于fill的配置,无法继续插入。
      • 已存在的entry是ziplist的头节点,当前quicklistNode节点前驱指针不为空,且是尾插
      • 前驱节点可以插入,因此插入在前驱节点的尾部。
      • 前驱节点不可以插入,因此要在当前节点和前驱节点之间新创建一个新节点保存要插入的entry。
      • 已存在的entry是ziplist的尾节点,当前quicklistNode节点后继指针不为空,且是前插
      • 后继节点可以插入,因此插入在前驱节点的头部。
      • 后继节点不可以插入,因此要在当前节点和后继节点之间新创建一个新节点保存要插入的entry。
      • 以上情况不满足,则属于将entry插入在ziplist中间的任意位置,需要分割当前quicklistNode节点。最后如果能够合并,还要合并

     插入函数

      1 /* Insert a new entry before or after existing entry 'entry'.
      2  *
      3  * If after==1, the new value is inserted after 'entry', otherwise
      4  * the new value is inserted before 'entry'. */
      5 //如果after为1,在已存在的entry后插入一个entry,否则在前面插入
      6 REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry,
      7                                    void *value, const size_t sz, int after) {
      8     int full = 0, at_tail = 0, at_head = 0, full_next = 0, full_prev = 0;
      9     int fill = quicklist->fill;
     10     quicklistNode *node = entry->node;
     11     quicklistNode *new_node = NULL;
     12 
     13     if (!node) {    //如果entry为没有所属的quicklistNode节点,需要新创建
     14         /* we have no reference node, so let's create only node in the list */
     15         D("No node given!");
     16         new_node = quicklistCreateNode();   //创建一个节点
     17         //将entry值push到new_node新节点的ziplist中
     18         new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
     19         //将新的quicklistNode节点插入到quicklist中
     20         __quicklistInsertNode(quicklist, NULL, new_node, after);
     21         //更新entry计数器
     22         new_node->count++;
     23         quicklist->count++;
     24         return;
     25     }
     26 
     27     /* Populate accounting flags for easier boolean checks later */
     28     //如果node不能插入entry
     29     if (!_quicklistNodeAllowInsert(node, fill, sz)) {
     30         D("Current node is full with count %d with requested fill %lu",
     31           node->count, fill);
     32         full = 1;   //设置full的标志
     33     }
     34 
     35     //如果是后插入且当前entry为尾部的entry
     36     if (after && (entry->offset == node->count)) {
     37         D("At Tail of current ziplist");
     38         at_tail = 1;    //设置在尾部at_tail标示
     39         //如果node的后继节点不能插入
     40         if (!_quicklistNodeAllowInsert(node->next, fill, sz)) {
     41             D("Next node is full too.");
     42             full_next = 1;  //设置标示
     43         }
     44     }
     45 
     46     //如果是前插入且当前entry为头部的entry
     47     if (!after && (entry->offset == 0)) {
     48         D("At Head");
     49         at_head = 1;    //设置at_head表示
     50         if (!_quicklistNodeAllowInsert(node->prev, fill, sz)) { //如果node的前驱节点不能插入
     51             D("Prev node is full too.");
     52             full_prev = 1;      //设置标示
     53         }
     54     }
     55 
     56     /* Now determine where and how to insert the new element */
     57     //如果node不满,且是后插入
     58     if (!full && after) {
     59         D("Not full, inserting after current position.");
     60         quicklistDecompressNodeForUse(node);    //将node临时解压
     61         unsigned char *next = ziplistNext(node->zl, entry->zi); //返回下一个entry的地址
     62         if (next == NULL) { //如果next为空,则直接在尾部push一个entry
     63             node->zl = ziplistPush(node->zl, value, sz, ZIPLIST_TAIL);
     64         } else {            //否则,后插入一个entry
     65             node->zl = ziplistInsert(node->zl, next, value, sz);
     66         }
     67         node->count++;  //更新entry计数器
     68         quicklistNodeUpdateSz(node);    //更新ziplist的大小sz
     69         quicklistRecompressOnly(quicklist, node);   //将临时解压的重压缩
     70 
     71     //如果node不满且是前插
     72     } else if (!full && !after) {
     73         D("Not full, inserting before current position.");
     74         quicklistDecompressNodeForUse(node);    //将node临时解压
     75         node->zl = ziplistInsert(node->zl, entry->zi, value, sz);   //前插入
     76         node->count++;  //更新entry计数器
     77         quicklistNodeUpdateSz(node);     //更新ziplist的大小sz
     78         quicklistRecompressOnly(quicklist, node);   //将临时解压的重压缩
     79 
     80     //当前node满了,且当前已存在的entry是尾节点,node的后继节点指针不为空,且node的后驱节点能插入
     81     //本来要插入当前node中,但是当前的node满了,所以插在next节点的头部
     82     } else if (full && at_tail && node->next && !full_next && after) {
     83         /* If we are: at tail, next has free space, and inserting after:
     84          *   - insert entry at head of next node. */
     85         D("Full and tail, but next isn't full; inserting next node head");
     86         new_node = node->next;  //new_node指向node的后继节点
     87         quicklistDecompressNodeForUse(new_node);    //将node临时解压
     88         new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_HEAD);  //在new_node头部push一个entry
     89         new_node->count++;  //更新entry计数器
     90         quicklistNodeUpdateSz(new_node);    //更新ziplist的大小sz
     91         quicklistRecompressOnly(quicklist, new_node);   //将临时解压的重压缩
     92 
     93     //当前node满了,且当前已存在的entry是头节点,node的前驱节点指针不为空,且前驱节点可以插入
     94     //因此插在前驱节点的尾部
     95     } else if (full && at_head && node->prev && !full_prev && !after) {
     96         /* If we are: at head, previous has free space, and inserting before:
     97          *   - insert entry at tail of previous node. */
     98         D("Full and head, but prev isn't full, inserting prev node tail");
     99         new_node = node->prev;  //new_node指向node的后继节点
    100         quicklistDecompressNodeForUse(new_node);    //将node临时解压
    101         new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_TAIL);//在new_node尾部push一个entry
    102         new_node->count++;  //更新entry计数器
    103         quicklistNodeUpdateSz(new_node);    //更新ziplist的大小sz
    104         quicklistRecompressOnly(quicklist, new_node);   //将临时解压的重压缩
    105 
    106     //当前node满了
    107     //要么已存在的entry是尾节点,且后继节点指针不为空,且后继节点不可以插入,且要后插
    108     //要么已存在的entry为头节点,且前驱节点指针不为空,且前驱节点不可以插入,且要前插
    109     } else if (full && ((at_tail && node->next && full_next && after) ||
    110                         (at_head && node->prev && full_prev && !after))) {
    111         /* If we are: full, and our prev/next is full, then:
    112          *   - create new node and attach to quicklist */
    113         D("	provisioning new node...");
    114         new_node = quicklistCreateNode();   //创建一个节点
    115         new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);  //将entrypush到new_node的头部
    116         new_node->count++;  //更新entry计数器
    117         quicklistNodeUpdateSz(new_node);        //更新ziplist的大小sz
    118         __quicklistInsertNode(quicklist, node, new_node, after);    //将new_node插入在当前node的后面
    119 
    120     //当前node满了,且要将entry插入在中间的任意地方,需要将node分割
    121     } else if (full) {
    122         /* else, node is full we need to split it. */
    123         /* covers both after and !after cases */
    124         D("	splitting node...");
    125         quicklistDecompressNodeForUse(node);    //将node临时解压
    126         new_node = _quicklistSplitNode(node, entry->offset, after);//分割node成两块
    127         new_node->zl = ziplistPush(new_node->zl, value, sz,
    128                                    after ? ZIPLIST_HEAD : ZIPLIST_TAIL);//将entry push到new_node中
    129         new_node->count++;  //更新entry计数器
    130         quicklistNodeUpdateSz(new_node);        //更新ziplist的大小sz
    131         __quicklistInsertNode(quicklist, node, new_node, after);    //将new_node插入进去
    132         _quicklistMergeNodes(quicklist, node);  //左右能合并的合并
    133     }
    134 
    135     quicklist->count++;     //更新总的entry计数器
    136 }

    3.2 push操作

    push一个entry到quicklist**头节点或尾节点中ziplist的头部或尾部**。底层调用了ziplistPush操作。

     1 /* Add new entry to head node of quicklist.
     2  *
     3  * Returns 0 if used existing head.
     4  * Returns 1 if new head created. */
     5 //push一个entry节点到quicklist的头部
     6 //返回0表示不改变头节点指针,返回1表示节点插入在头部,改变了头结点指针
     7 int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
     8     quicklistNode *orig_head = quicklist->head; //备份头结点地址
     9 
    10     //如果ziplist可以插入entry节点
    11     if (likely(
    12             _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
    13         quicklist->head->zl =
    14             ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);  //将节点push到头部
    15         quicklistNodeUpdateSz(quicklist->head); //更新quicklistNode记录ziplist大小的sz
    16     } else {        //如果不能插入entry节点到ziplist
    17         quicklistNode *node = quicklistCreateNode();    //新创建一个quicklistNode节点
    18 
    19         //将entry节点push到新创建的quicklistNode节点中
    20         node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
    21 
    22         quicklistNodeUpdateSz(node);    //更新ziplist的大小sz
    23         _quicklistInsertNodeBefore(quicklist, quicklist->head, node);   //将新创建的节点插入到头节点前
    24     }
    25     quicklist->count++;                     //更新quicklistNode计数器
    26     quicklist->head->count++;               //更新entry计数器
    27     return (orig_head != quicklist->head);  //如果改变头节点指针则返回1,否则返回0
    28 }
    29 
    30 /* Add new entry to tail node of quicklist.
    31  *
    32  * Returns 0 if used existing tail.
    33  * Returns 1 if new tail created. */
    34 //push一个entry节点到quicklist的尾节点中,如果不能push则新创建一个quicklistNode节点
    35 //返回0表示不改变尾节点指针,返回1表示节点插入在尾部,改变了尾结点指针
    36 int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
    37     quicklistNode *orig_tail = quicklist->tail;
    38 
    39     //如果ziplist可以插入entry节点
    40     if (likely(
    41             _quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {
    42         quicklist->tail->zl =
    43             ziplistPush(quicklist->tail->zl, value, sz, ZIPLIST_TAIL);  //将节点push到尾部
    44         quicklistNodeUpdateSz(quicklist->tail); //更新quicklistNode记录ziplist大小的sz
    45     } else {
    46         quicklistNode *node = quicklistCreateNode();        //新创建一个quicklistNode节点
    47 
    48         //将entry节点push到新创建的quicklistNode节点中
    49         node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL);
    50 
    51         quicklistNodeUpdateSz(node);        //更新ziplist的大小sz
    52         _quicklistInsertNodeAfter(quicklist, quicklist->tail, node);//将新创建的节点插入到尾节点后
    53     }
    54     quicklist->count++;             //更新quicklistNode计数器
    55     quicklist->tail->count++;       //更新entry计数器
    56     return (orig_tail != quicklist->tail);  //如果改变尾节点指针则返回1,否则返回0
    57 }

    上层调用函数

    1 /* Wrapper to allow argument-based switching between HEAD/TAIL pop */
    2 void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
    3                    int where) {
    4     if (where == QUICKLIST_HEAD) {
    5         quicklistPushHead(quicklist, value, sz);//头插法
    6     } else if (where == QUICKLIST_TAIL) {
    7         quicklistPushTail(quicklist, value, sz);//尾插法
    8     }
    9 }

    3.3 pop操作
      从quicklist的头节点或尾节点的ziplist中pop出一个entry,分该entry保存的是字符串还是整数。如果字符串的话,需要传入一个函数指针,这个函数叫_quicklistSaver(),真正的pop操作还是在这两个函数基础上在封装了一次,来操作拷贝字符串的操作。

     1 // 接口函数,执行POP操作
     2 // 执行成功返回1,反之0
     3 // 如果弹出节点是字符串值,data,sz存放弹出节点的字符串值
     4 // 如果弹出节点是整型值,slong存放弹出节点的整型值
     5 int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
     6                  unsigned int *sz, long long *slong) {
     7     unsigned char *vstr;
     8     unsigned int vlen;
     9     long long vlong;
    10     // 没有数据项,直接返回
    11     if (quicklist->count == 0)
    12         return 0;
    13     // 调用底层实现函数
    14     // 传入的_quicklistSaver是一个函数指针,用于深拷贝节点的值,用于返回
    15     int ret = quicklistPopCustom(quicklist, where, &vstr, &vlen, &vlong,
    16                                  _quicklistSaver);
    17     // 给data,sz,slong赋值
    18     if (data)
    19         *data = vstr;
    20     if (slong)
    21         *slong = vlong;
    22     if (sz)
    23         *sz = vlen;
    24     return ret;
    25 }
    26 // pop操作的底层实现函数
    27 // 执行成功返回1,反之0
    28 // 如果弹出节点是字符串值,data,sz存放弹出节点的字符串值
    29 // 如果弹出节点是整型值,slong存放弹出节点的整型值
    30 int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
    31                        unsigned int *sz, long long *sval,
    32                        void *(*saver)(unsigned char *data, unsigned int sz)) {
    33     unsigned char *p;
    34     unsigned char *vstr;
    35     unsigned int vlen;
    36     long long vlong;
    37     // 判断弹出位置,首部或者尾部
    38     int pos = (where == QUICKLIST_HEAD) ? 0 : -1;
    39     // 没有数据
    40     if (quicklist->count == 0)
    41         return 0;
    42     // 
    43     if (data)
    44         *data = NULL;
    45     if (sz)
    46         *sz = 0;
    47     if (sval)
    48         *sval = -123456789;
    49     // 获取quicklist节点
    50     quicklistNode *node;
    51     if (where == QUICKLIST_HEAD && quicklist->head) {
    52         node = quicklist->head;
    53     } else if (where == QUICKLIST_TAIL && quicklist->tail) {
    54         node = quicklist->tail;
    55     } else {
    56         return 0;
    57     }
    58     // 获取ziplist中的节点
    59     p = ziplistIndex(node->zl, pos);
    60     // 获取该节点的值
    61     if (ziplistGet(p, &vstr, &vlen, &vlong)) {
    62         // 如果是字符串值
    63         if (vstr) {
    64             if (data)
    65                 // _quicklistSaver函数用于深拷贝取出返回值
    66                 *data = saver(vstr, vlen);
    67             if (sz)
    68                 *sz = vlen;  // 字符串的长度
    69         } else {
    70             // 如果存放的是整型值
    71             if (data)
    72                 *data = NULL;  // 字符串设为NULL
    73             if (sval)
    74                 *sval = vlong;  // 弹出节点的整型值
    75         }
    76         // 删除该节点
    77         quicklistDelIndex(quicklist, node, &p);
    78         return 1;
    79     }
    80     return 0;
    81 }
    82 // 返回一个字符串副本,深拷贝
    83 // 这里深拷贝的用意是避免二次释放
    84 REDIS_STATIC void *_quicklistSaver(unsigned char *data, unsigned int sz) {
    85     unsigned char *vstr;
    86     if (data) {
    87         vstr = zmalloc(sz);
    88         memcpy(vstr, data, sz);
    89         return vstr;
    90     }
    91     return NULL;
    92 }

    四、其他接口函数

    Redis关于quicklist还提供了很多接口函数。

     1 // 在quicklist尾部追加指针zl指向的ziplist
     2 void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl);
     3 // 将ziplist数据转换成quicklist
     4 quicklist *quicklistCreateFromZiplist(int fill, int compress,
     5                                       unsigned char *zl);
     6 // 在node节点后添加一个值valiue
     7 void quicklistInsertAfter(quicklist *quicklist, quicklistEntry *node,
     8                           void *value, const size_t sz);
     9 // 在node节点前面添加一个值value
    10 void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *node,
    11                            void *value, const size_t sz);
    12 // 删除ziplist节点entry
    13 void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry);
    14 // 翻转quicklist
    15 void quicklistRotate(quicklist *quicklist);
    16 // 返回quicklist列表中所有数据项的个数总和
    17 unsigned int quicklistCount(quicklist *ql);
    18 // 比较两个quicklist结构数据
    19 int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
    20 // 从节点node中取出LZF压缩编码后的数据
    21 size_t quicklistGetLzf(const quicklistNode *node, void **data);

    五、quicklist小结
      quicklist将sdlist和ziplist两者的优点结合起来,在时间和空间上做了一个均衡,能较大程度上提高Redis的效率。压入和弹出操作的时间复杂度都很理想。
    参考文章:

    https://blog.csdn.net/terence1212/article/details/53770882

    https://blog.csdn.net/men_wen/article/details/70229375?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.base&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.base

    本文来自博客园,作者:Mr-xxx,转载请注明原文链接:https://www.cnblogs.com/MrLiuZF/p/14995102.html

  • 相关阅读:
    Java FileInputStream与FileReader的区别
    java 保存和读取本地json文件
    java写文件时往末尾追加文件(而不是覆盖原文件),的两种方法总结
    Java魔法堂:注解用法详解——@SuppressWarnings
    使用Restlet Client发送各种Get和Post请求
    postman VS restlet client基本使用
    Java获取请求客户端的真实IP地址
    dom4j解析xml字符串实例
    C++ Boost库简介(一些自己的感受)
    打仗其实最讲成本核算,大炮轰的都是黄金,日军在中国就是不断赔本
  • 原文地址:https://www.cnblogs.com/MrLiuZF/p/14995102.html
Copyright © 2011-2022 走看看