redis6.0 源码学习(五)ziplist

redis6.0源码学习(五)ziplist文章目录redis6.0源码学习(五)ziplist一、数据结构二、代码解析1、创建2、查找3、插入三、总结一、数据结构ziplist是经过特殊编码的双向链接列表,该列表具有很高的内存效率。它存储字符串和整数值,其中整数被编码为实际整数,而不是一系列个字符。它允许对列表的两侧进行push和pop操作且复杂度为O(1)。但是由于每个操作都需要重新分配ziplist使用的内存,实际复杂度与ziplist使用的内存量有关。下图是ziplist得示意图:

大家好,又见面了,我是你们的朋友全栈君。

redis6.0 源码学习(五)ziplist

一、数据结构

ziplist是经过特殊编码的双向链接列表,该列表具有很高的内存效率。 它存储字符串和整数值,其中整数被编码为实际整数,而不是一系列个字符。 它允许对列表的两侧进行push和pop操作且复杂度为O(1)。 但是由于每个操作都需要重新分配ziplist使用的内存,实际复杂度与ziplist使用的内存量有关。

下图是ziplist得示意图:

在这里插入图片描述)

各个字段详细解释如下:

属性 类型 长度 用途
zlbytes uint32_t 4字节 记录整个压缩列表占用的内存字节数,在对压缩列表进行内存重分配或者计算zlend的位置时使用。
zltail uint32_t 4字节 记录压缩列表尾节点距离压缩列表起始地址有多少个字节,通过这个偏移量,程序可以直接获得压缩列表的表尾节点地址
zllen uint16_t 2字节 记录了压缩列表的节点数量。当这个属性的值小于65535时(即:小于UINT16_MAX),这个属性的值就是压缩列表的节点数量,但是当这个值等于UINT16_MAX时,需要遍历整个压缩列表才能获得其节点数量
entryX 列表节点 不定 压缩表的各个节点,X代表数量不定
zlend uint8_t 1字节 特殊常数值(OxFF,也就是255)。用于标记压缩列表的末端。

ziplist中的每个entry均包含两个信息:prevlen 和 encoding。prevlen 字段:存储前一个条目的长度,以便能够从后到前遍历列表。encoding表示entry类型,整数或字符串,对于字符串,还表示字符串有效负载的长度。 因此,完整的entry存储形式如下:

在这里插入图片描述)

在存储较小的整数的时候,entry-data字段会不存在,因为也存储在encoding字段中了。

1、prevlen:针对prevlen的大小不同,prevlen占用的字段长度也不一样。

  • prevlen < 254

    prevlen占用1个字节 8位

  • prevlen > 254

    此时prevlen占用5个字节,第一个字节为 0xFE,后续四个字节为真正的长度。 0xFE表示是下一个entry一个大的值。

2、encoding 根据entry-data存入的是字符串还是整数具有不同的格式。具体如下:

  • a:字符串

在这里插入图片描述

当字符串小于63字节时(2^6),节点存为上图的第一种类型,高2位为00,低6位表示data的长度。

当字符串小于16383字节时(2^14),节点存为上图的第二种类型,高2位为01,后续14位表示data的长度。

当字符串小于4294967296字节时(2^32),节点存为上图的第三种类型,高2位为10,下一字节起连续32位表示data的长度。

  • b:整数:整数节点的encoding的长度为8位,其中高2位用来区分整数节点和字符串节点(高2位为11时是整数节点),低6位用来区分整数节点的类型,具体如下:

在这里插入图片描述

二、代码解析

主要有以下接口,这里只分析几个,其他的大家可以自行去看源代码。

unsigned char *ziplistNew(void);
unsigned char *ziplistMerge(unsigned char **first, unsigned char **second);
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where);
unsigned char *ziplistIndex(unsigned char *zl, int index);
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p);
unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p);
unsigned int ziplistGet(unsigned char *p, unsigned char **sval, unsigned int *slen, long long *lval);
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen);
unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p);
unsigned char *ziplistDeleteRange(unsigned char *zl, int index, unsigned int num);
unsigned int ziplistCompare(unsigned char *p, unsigned char *s, unsigned int slen);
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip);
unsigned int ziplistLen(unsigned char *zl);
size_t ziplistBlobLen(unsigned char *zl);

1、创建

可以对照图1 ziplist结构示意图 看这个创建的过程。

/* Create a new empty ziplist. */
unsigned char *ziplistNew(void) { 
   
    unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE;  //<zlbytes>4字节<zltail>4字节<zllen>2字节<zlend>1字节,没有entry节点
    unsigned char *zl = zmalloc(bytes); //申请内存
    ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);//zlbytes赋值
    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);//zltail赋值
    ZIPLIST_LENGTH(zl) = 0; //zllen赋值
    zl[bytes-1] = ZIP_END; //zlend赋值
    return zl;
}

2、查找

/* Find pointer to the entry equal to the specified entry. Skip 'skip' entries * between every comparison. Returns NULL when the field could not be found. */
//返回p节点之后data与vstr(长度是vlen)相等的节点,只找p节点之后每隔skip的节点
//时间复杂度 O(n)
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip) { 
   
    int skipcnt = 0;
    unsigned char vencoding = 0;
    long long vll = 0;

    while (p[0] != ZIP_END) { 
     /* ZIP_END: Special "end of ziplist" entry. */
        unsigned int prevlensize, encoding, lensize, len;
        unsigned char *q;

        ZIP_DECODE_PREVLENSIZE(p, prevlensize); //获取prevlen的字节数1还是5
        /* 获取'ptr'中 entry encoding type 和 data length (字符串长度或者整数使用的bytes ) 。 * encoding变量: entry的encoding值 *lensize变量:encode entry所需要的bytes数 *len变量: hold the entry length. */        
        ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);
        q = p + prevlensize + lensize; //当前节点的data

        if (skipcnt == 0) { 
   
            /* Compare current entry with specified entry */
            if (ZIP_IS_STR(encoding)) { 
   //判断当前节点是不是字符串节点
                if (len == vlen && memcmp(q, vstr, vlen) == 0) { 
    //字符串比较
                    return p;
                }
            } else { 
   
                /* Find out if the searched field can be encoded. Note that * we do it only the first time, once done vencoding is set * to non-zero and vll is set to the integer value. */
                if (vencoding == 0) { 
    //这个代码块只会执行一次,判断vstr能够用整数表示 
                    /* 校验'vstr'指向的内容能否转换成整数,'vll'存这个整数值 'encoding'存编码格式 */
                    if (!zipTryEncoding(vstr, vlen, &vll, &vencoding)) { 
   
                        /* If the entry can't be encoded we set it to * UCHAR_MAX so that we don't retry again the next * time. */
                        vencoding = UCHAR_MAX;
                    }
                    /* Must be non-zero by now */
                    assert(vencoding); 
                }

                /* Compare current entry with specified entry, do it only * if vencoding != UCHAR_MAX because if there is no encoding * possible for the field it can't be a valid integer. */
                if (vencoding != UCHAR_MAX) { 
    //entry地址的内容转换成整数
                    long long ll = zipLoadInteger(q, encoding);
                    if (ll == vll) { 
   
                        return p; //相等的话返回
                    }
                }
            }

            /* Reset skip count */
            skipcnt = skip;
        } else { 
   
            /* Skip entry */
            skipcnt--;
        }

        /* Move to next entry */
        p = q + len;
    }

    return NULL;
}

3、插入

/* Insert an entry at "p". */
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) { 
   
    return __ziplistInsert(zl,p,s,slen);
}

/* Insert item at "p". */
unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) { 
   
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen; // 当前长度和插入节点后需要的长度
    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[0]不指向列表末端,说明列表非空,并且p指向其中一个节点
    if (p[0] != ZIP_END) { 
   
         // 获取前置节点p的长度和编码该长度需要的字节
        ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
    } else { 
           
        unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
        if (ptail[0] != ZIP_END) { 
   // 如果p指向列表末端,表示列表为空
            prevlen = zipRawEntryLength(ptail);
        }
    }

    /* See if the entry can be encoded */
    if (zipTryEncoding(s,slen,&value,&encoding)) { 
   //尝试encoding成整数
        /* 'encoding' is set to the appropriate integer encoding */
        reqlen = zipIntSize(encoding); //获取编码长度
    } else { 
   
        /* 'encoding' is untouched, however zipStoreEntryEncoding 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 += zipStorePrevEntryLength(NULL,prevlen); // 获取前置节点的编码长度
    reqlen += zipStoreEntryEncoding(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. */
    // 只要不是列表的末端插入,需要计算出下一个元素保存本元素prevlen字段空间是否足够, 不够时计算出欠缺的差值 
    // nextdiff大于0,那就说明需要对当前p指向的节点的header进行扩展
    int forcelarge = 0;
    nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
    if (nextdiff == -4 && reqlen < 4) { 
   
        nextdiff = 0;
        forcelarge = 1;
    }

    /* Store offset because a realloc may change the address of zl. */
    offset = p-zl;// 存储p相对于列表zl的偏移地址
    zl = ziplistResize(zl,curlen+reqlen+nextdiff); // 重新分配空间,curlen当前列表的长度
    p = zl+offset; // 重新获取p的值

    /* Apply memory move when necessary and update tail offset. */
    if (p[0] != ZIP_END) { 
    // 非表尾插入,需要重新计算表尾的偏移量
        /* Subtract one because of the ZIP_END bytes */
        memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);/* 移动p原有位置和后面的内容到新的位置 */      

        /* Encode this entry's raw length in the next entry. */   
        if (forcelarge)
            zipStorePrevEntryLengthLarge(p+reqlen,reqlen);
        else
            zipStorePrevEntryLength(p+reqlen,reqlen);

        /* Update offset for tail */
        // 更新列表尾相对于表头的偏移量,将新节点的长度算上
        ZIPLIST_TAIL_OFFSET(zl) =
            intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);

        /* When the tail contains more than one entry, we need to take * "nextdiff" in account as well. Otherwise, a change in the * size of prevlen doesn't have an effect on the *tail* offset. */
        // 如果新节点后面有多个节点,那么表尾的偏移量需要算上nextdiff的值
        zipEntry(p+reqlen, &tail);
        if (p[reqlen+tail.headersize+tail.len] != ZIP_END) { 
   
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
        }
    } else { 
   
        // 表尾插入,直接计算偏移量
        /* This element will be the new tail. */
        ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);
    }

    /* When nextdiff != 0, the raw length of the next entry has changed, so * we need to cascade the update throughout the ziplist */
     // 当nextdiff不为0时,表示需要新节点的后继节点对头部进行扩展
    //说明下一个元素需要扩展空间存放prevlen字段, 由于下一个元素空间变大, 有可能引起下下一个元素空间需要扩展, 下面函数检测后面元素, 并在需要时重置元素prevlen长度
    if (nextdiff != 0) { 
   
        // 需要对p所指向的header进行扩展更新
        // 有可能会引起连锁更新
        offset = p-zl;
        zl = __ziplistCascadeUpdate(zl,p+reqlen);
        p = zl+offset;
    }

    /* Write the entry */
    p += zipStorePrevEntryLength(p,prevlen); // 将新节点前置节点的长度写入新节点的header
    p += zipStoreEntryEncoding(p,encoding,slen); // 将新节点的值长度写入新节点的header
    if (ZIP_IS_STR(encoding)) { 
    // 写入节点值
        memcpy(p,s,slen);
    } else { 
   
        zipSaveInteger(p,value,encoding);
    }
    ZIPLIST_INCR_LENGTH(zl,1);  // 更新列表节点计数
    return zl;
}

三、总结

  • ziplist是为节省内存空间而生的。
  • ziplist是一个为Redis专门提供的底层数据结构之一,本身可以有序也可以无序。当作hash的底层实现时,节点之间没有顺序;当作为zset的底层实现时,节点之间会按照大小顺序排列。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/128953.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • mysql databasemetadata_DatabaseMetaData的用法(转)

    mysql databasemetadata_DatabaseMetaData的用法(转)一.得到这个对象的实例Connectioncon;con=DriverManager.getConnection(url,userName,password);DatabaseMetaDatadbmd=con.getMetaData();二.方法getTables的用法原型:ResultSetDatabaseMetaData.getTables(Stringcatalog,…

    2022年6月19日
    22
  • JVM调优工具「建议收藏」

    JVM调优工具「建议收藏」JVM调优工具Jconsole:jdk自带,功能简单,但是可以在系统有一定负荷的情况下使用。对垃圾回收算法有很详细的跟踪。JProfiler:商业软件,需要付费。功能强大。VisualVM:JDK自带,功能强大,与JProfiler类似。推荐。如何调优观察内存释放情况、集合类检查、对象树上面这些调优工具都提供了强大的功能,但是总的来说一般分为以下几类功能堆信息查…

    2022年6月1日
    29
  • vue支持es6_vue2转vue3

    vue支持es6_vue2转vue3转载:Vue2.0ES6语法降级ES5由于部分低版本的手机还不支持ES6语法,将会导致vue报错。综合了网上的各种办法,我的项目现在终于成功降级ES5.首先安装插件npminstall-Dbabel-preset-es2015babel-corebabel-preset-stage-2babel-loader编辑配置文件…

    2022年9月25日
    0
  • MODIS数据介绍

    MODIS数据介绍转自:http://blog.sina.com.cn/s/blog_53e9bb570101jv55.html一、Modis数据资源总体介绍&nbsp;1999年2月18日,美国成功地发射了地球观测系统(EOS)的第一颗先进的极地轨道环境遥感卫星Terra。它的主要目标是实现…

    2022年5月7日
    72
  • spring在LInux下出现的问题【转】

    spring在LInux下出现的问题【转】

    2022年3月2日
    33
  • python中lambda函数「建议收藏」

    python中lambda函数「建议收藏」python中lambda被称为行内函数或者匿名函数代码简洁性和便用性

    2022年7月5日
    21

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号