• Redis 3.0.4 压缩列表


      压缩列表是列表键和哈希键的底层实现之一。

      1.压缩列表的构成

        1.1.ziplist结构如图  

      1. zlbytes(4字节) : 表示压缩表的总长;
      2. zltail(4字节):表示压缩列表表尾节点距离列表的起始地点有多少字节;
      3. zllen(2字节):表示压缩列表包含的节点数量(当值小于uint16_max 65535时,节点的真实数量需要遍历整个压缩列表才能得出);
      4. entryX(不定):表示压缩列表的各个节点节点的长度由节点保存的内容决定;
      5. zlend(1字节):特殊值0xFF(255),用于标记压缩列表的末尾。
    //将zl定义到前四个字节的bytes成员 记录整个压缩列表的内存字节数
    #define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))
    //将zl定位到4字节到8字节的tail_offset成员 记录整个压缩列表的内存字节数
    #define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
    //将zl定位到8字节到10字节的length成员,记录着压缩列表的节点数量
    #define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
    //压缩列表表头的大小10个字节
    #define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))
    //返回压缩列表首节点的地址
    #define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)
    //返回压缩列表尾节点的地址
    #define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
    //返回end成员的地址 一个字节
    #define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)

        1.2.创建一个空的压缩列表

    //压缩列表总共有11个字节的固定长度
    //创建并返回一个新的压缩列表
    unsigned char *ziplistNew(void) {
        //ZIPLIST_HEADER_SIZE 是压缩列表的表头 1字节是末端的end大小
        unsigned int bytes = ZIPLIST_HEADER_SIZE+1;
        unsigned char *zl = zmalloc(bytes);  //为表头和表尾end成员分配空间
        ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);   //将bytes成员初始化为bytes=11
        ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE); //空列表的tail_offset成员为表头大小为10字节
        ZIPLIST_LENGTH(zl) = 0; //字节数量为0
        zl[bytes-1] = ZIP_END;   //将表尾end成员设置默认的255
        return zl;
    }

      2.压缩列表节点的构成

    typedef struct zlentry {
        //prevrawlen 前驱节点的长度
        //prevrawlensize 编码前驱节点的长度prevrawlen所需的字节大小
        unsigned int prevrawlensize, prevrawlen;
        //len 当前节点值长度
        //lensize 编码当前节点长度len所需的字节数
        unsigned int lensize, len;
        //当前节点header的大小 = lensize + prevrawlensize
        unsigned int headersize;
        //当前节点的编码方式
        unsigned char encoding;
        //指向当前节点的指针,以char *类型保存
        unsigned char *p;
    } zlentry; //压缩列表节点信息的结构

        压缩列表节点的定义是zlentry,但是实质上存储节点是利用了以下的结构

        1. previous_entry_length:记录前驱节点的长度;
        2. encoding:即记录当前节点的value成员的数据类型及长度;
        3. value:根据encoding来保存字节数组或者整数。 

          2.1 prevois_entry_length

            记录前驱节点的长度,以字节为单位。长度可以是1字节或者5字节。

            1>. 如果前一字节的长度小于254,那么previous_entry_length长度为1字节,

            2>.如果前一字节的长度大于等于254,那么previous_entry_length长度为5字节,其中第一个字节是0XFE(254),而之后的四个字节表示前一节点的长度。

          2.2 encoding

          记录了节点的content属性所保存数据的类型以及长度。

            1.一字节、两字节或者五字节,值的最高位为00、01、10的字节数组编码,这种编码表示节点的content属性保存着字节数组,字节数组的长度由编码出掉最高两位之后的其他记录。

            2.一字节长,值的最高位以11开头的是整数编码:这种编码表示节点的content属性保存着整数值,整数值的类型和长度由编码除去最高两位之后的其他记录。

          2.3 content

            节点的content属性负责保存节点的值,节点值可以是一个节点数组或者整数,值的类型和长度由节点的encoding属性决定。

      3.连锁更新

        在压缩列表节点的结构里previous_entry_length是记录前一个节点的长度,如果前一个节点的长度小于254,那么previous_entry_length需要1个字节空间保存前驱节点的长度值,如果前一个节点的长度大于等于254,那么previous_entry_length需要5个节点空间保存前驱节点的长度值。

        假设存在一种情况,压缩列表里面的节点长度都是小于254字节,e1~eN字节数都在250~253之间,这时有一个长度大于254字节的new节点,需要插入到e1前,插入之后由于e1节点的previoud_entry_length是一个字节的,不能保存new节点的字节长度,需要碎e1节点进行扩展,同理,e1扩展之后字节大小大于等于254,则e1的后继节点e2节点的previous_entry_length不能保存e1扩展知乎的字节长度,也需要对e2扩展,以此类推,之后的节点都需要扩展,所以连锁更新最坏的情况下需要对压缩列表执行N次空间重新分配操作,而每次空间分配操作的最坏的时间复杂度是O(n),所以连锁更新最坏的时间复杂度是O(N^2)。

        如果e1~eN字节数都是大于254,此时有一个长度小于254的new节点需要插入到e1前,也会产生连锁更新,并且在删除节点也会有连锁更新。

      连锁更新的代码如下:

    //将列表zk的p指针针对的next节点进行移动
    //
    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;
        //ZIP_END标识尾节点
        while (p[0] != ZIP_END) {
            cur = zipEntry(p);
            rawlen = cur.headersize + cur.len;
            rawlensize = zipPrevEncodeLength(NULL,rawlen); //计算当前节点长度所需的字节数
    
            /* Abort if there is no next entry. */
            if (p[rawlen] == ZIP_END) break;
            next = zipEntry(p+rawlen);  //获取p的next节点
    
            /* Abort when "prevlen" has not changed. */
            if (next.prevrawlen == rawlen) break;
            //如果next节点存储的前驱节点长度所需的字节大小 小于 前驱节点所需的字节大小
            if (next.prevrawlensize < rawlensize) {
                /* The "prevlen" field of "next" needs more bytes to hold
                 * the raw length of "cur". */
                offset = p-zl; //p节点的偏移量
                extra = rawlensize-next.prevrawlensize; //需要额外增长的字节大小
                zl = ziplistResize(zl,curlen+extra); //resize 链表所需空间大小
                p = zl+offset;
    
                /* Current pointer and offset for next element. */
                np = p+rawlen;  //next节点的新地址
                noffset = np-zl;  //next节点的偏移量
                //调整列表zltail的大小
                /* Update tail offset when next element is not the tail element. */
                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. */
                //更新 移动next节点除header的prevrawlensize之外的剩余list的内存
                //从next节点的prev_entry_len字段之后的内存开始 到zlend之前的内容 
                memmove(np+rawlensize,
                    np+next.prevrawlensize,
                    curlen-noffset-next.prevrawlensize-1);
                //更新  将next节点的header以rawlen长度重新编码,更新prevrawlensize和prevrawlen 
                //更新next节点的previous_entry_length字段为rawlen
                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. */
                    zipPrevEncodeLengthForceLarge(p+rawlen,rawlen);
                } else {
                    zipPrevEncodeLength(p+rawlen,rawlen);
                }
    
                /* Stop here, as the raw length of "next" has not changed. */
                break;
            }
        }
        return zl;
    }

      是根据给定的zl节点,然后通过更新p节点针对的next节点进行移动。首先获取p节点的字节长度和长度,来和p的next节点保存的previous_entry_length比较大小,如果next.prevrawlensize < rawlensize,那么需要对next.prev_entry_len字段之后的内存开始,到zlend之前的内容,进行memmove,来为next节点记录其前驱节点获取到足够的字节大小。

      4.删除节点

      从ziplist中,p指向的节点开始,最多删除num个节点

    //从p节点开始最多删除num个节点
    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;
    
        first = zipEntry(p);
        for (i = 0; p[0] != ZIP_END && i < num; i++) {
            p += zipRawEntryLength(p); //需要删除的总字节数
            deleted++; //需要删除的节点数
        }
    
        totlen = p-first.p; //需要删除的总字节数
        if (totlen > 0) {
            if (p[0] != ZIP_END) {
                /* Storing `prevrawlen` in this entry may increase or decrease the
                 * number of bytes required compare to the current `prevrawlen`.
                 * There always is room to store this, because it was previously
                 * stored by an entry that is now being deleted. */
                nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);
                p -= nextdiff;  
                zipPrevEncodeLength(p,first.prevrawlen);
                
                /* Update offset for tail */
                //更新zltail  表尾据压缩列表的起始地址的字节数
                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. */
                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 */
                //移动p节点到zlend节点至first.p 
                memmove(first.p,p,
                    intrev32ifbe(ZIPLIST_BYTES(zl))-(p-zl)-1);
            } else {
                /* The entire tail was deleted. No need to move memory. */
                //p节点之后的所有元素都删除了
                ZIPLIST_TAIL_OFFSET(zl) =
                    intrev32ifbe((first.p-zl)-first.prevrawlen);
            }
            * Resize and update length */
            offset = first.p-zl;
            zl = ziplistResize(zl, intrev32ifbe(ZIPLIST_BYTES(zl))-totlen+nextdiff);
            ZIPLIST_INCR_LENGTH(zl,-deleted);
            p = zl+offset;
    
            /* When nextdiff != 0, the raw length of the next entry has changed, so
             * we need to cascade the update throughout the ziplist */
            //表示p节点的prev_raw_len_size需要扩容或者缩容  需要连锁更新 
           if (nextdiff != 0)
                zl = __ziplistCascadeUpdate(zl,p);
        }
        return zl;
    }

      删除节点,需要知道要删除的字节长度,和节点数,需要处理的是如果删除完之后的后续节点不是zlend,则要更新p节点的prevrawlen,并且更新zltail的长度。

      5.插入节点

        插入到p所指向的节点

    /* Insert item at "p". */
    //插入到p所指向的节点
    static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
        size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen;
        unsigned int prevlensize, prevlen = 0;
        size_t offset;
        int nextdiff = 0;
        unsigned char encoding = 0;
        long long value = 123456789; /* initialized to avoid warning. Using a value
                                        that is easy to see if for some reason
                                        we use it uninitialized. */
        zlentry tail;
    
    
        /* Find out prevlen for the entry that is inserted. */
        //获取p的前继节点的长度prevlen
        if (p[0] != ZIP_END) {
            ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
        } else {
            unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
            if (ptail[0] != ZIP_END) {
                prevlen = zipRawEntryLength(ptail);
            }
        }
    
        /* See if the entry can be encoded */
        //计算s节点的value encoding
        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. */
        //获取新节点的总长度
        reqlen += zipPrevEncodeLength(NULL,prevlen);
        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. */
        //c节点要插入到p节点的前面  计算p的prev_entry_len 和要插入的s节点的节点长度的差值
        nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
    
        /* Store offset because a realloc may change the address of zl. */
        offset = p-zl;
        zl = ziplistResize(zl,curlen+reqlen+nextdiff);
        p = zl+offset;
    
        /* Apply memory move when necessary and update tail offset. */
        if (p[0] != ZIP_END) {
            /* Subtract one because of the ZIP_END bytes */
            //p+reqlen表示p节点的新地址  nextdiff表示前继节点是否需要增大或者减少  
            //curlen-offset-1+nextdiff  表示移动长度
            memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);
    
            /* Encode this entry's raw length in the next entry. */
            //更新移动之后的p节点的prev_raw_length
            zipPrevEncodeLength(p+reqlen,reqlen);
    
            /* Update offset for tail */
            //更新 zltail
            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. */
            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 */
        if (nextdiff != 0) {
            offset = p-zl;
            zl = __ziplistCascadeUpdate(zl,p+reqlen);
            p = zl+offset;
        }
    
        /* Write the entry */
        p += zipPrevEncodeLength(p,prevlen);
        p += zipEncodeLength(p,encoding,slen);
        if (ZIP_IS_STR(encoding)) {
            memcpy(p,s,slen);
        } else {
            zipSaveInteger(p,value,encoding);
        }
        ZIPLIST_INCR_LENGTH(zl,1);
        return zl;
    }

          

  • 相关阅读:
    jQuery动画(带参数)
    ajax是什么
    jQuery遍历(3)
    jQuery遍历(2)
    jQuery遍历(1)
    javascript错误处理
    导航菜单的实现
    bootstrap环境搭建
    jQuery动画效果
    MVVM模式下WPF动态绑定展示图片
  • 原文地址:https://www.cnblogs.com/chenyang920/p/13205314.html
Copyright © 2020-2023  润新知