redis源码浅析-ziplist实现

news2024/12/23 10:12:45

redis中的list是有多种实现的,其中一种是ziplist,其介绍如下

ziplist 是一个经过特殊编码的双向链表,旨在提高内存效率。 它存储字符串和整数值,其中整数被编码为实际整数而不是一系列字符。 它允许在 O(1) 时间内在列表的任一侧进行推送和弹出操作。 但是,由于每个操作都需要重新分配 ziplist 使用的内存,因此实际复杂性与 ziplist 使用的内存量有关。

ziplist是一个双向链表结构,是一整块紧凑的内存块,当大小不足需要重新扩展,底层使用je_realloc进行扩展。

typedef struct zlentry {
    unsigned int prevrawlensize; // prevrawlen字段的字节数大小,前一节点的大小的类型,1字节或者5字节
    unsigned int prevrawlen;     // 前一个节点的的长度
    unsigned int lensize;       // len字段的字节数大小
    unsigned int len;          
    unsigned int headersize;     /* prevrawlensize + lensize. */
    unsigned char encoding;      /* Set to ZIP_STR_* or ZIP_INT_* depending on
                                    the entry encoding. However for 4 bits
                                    immediate integers this can assume a range
                                    of values and must be range-checked. */
    unsigned char *p;            /* Pointer to the very start of the entry, that
                                    is, this points to prev-entry-len field. */
} zlentry;

zipList中在实际计算的时候,使用zlentry来表示每个节点,其中的prevrawlen是前一个节点的大小,len表示的是当前节点的大小,由于整个ziplist是一整块的内存块,通过prevrawlenlen字段,我们就能够向前或向后便利整个链表。
在这里插入图片描述
需要注意的是,ziplist中的节点并不是一个zlentry,而是在计算的时候使用zlentry来模拟,实际ziplist中的每个节点,大概是如下结构``: ![在这里插入图片描述](https://img-blog.csdnimg.cn/cb20b5e505d841e1b12fb0aa9015e587.png) 通过zipEntry函数,将ziplist中一个entry节点信息,填充到一个zlentry中,这二者只是共享了同一个实际数据p`的指针。

static inline void zipEntry(unsigned char *p, zlentry *e) {
    ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen);
    ZIP_ENTRY_ENCODING(p + e->prevrawlensize, e->encoding);
    ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len);
    assert(e->lensize != 0); /* check that encoding was valid. */
    e->headersize = e->prevrawlensize + e->lensize;
    e->p = p;
}

整个ziplist的结构如上:zlbytes表示的是整个ziplist所占用的空间大小,zltail是尾节点的位置,zllen表示的是当前有多少个节点,zlend是一个标志位,表示的是链表的结尾。一个byte,为255.

在创建ziplist的时候,初始的时候只创建了一个空的ziplist表头:

/* Create a new empty ziplist. */
//创建一个空的ziplist
unsigned char *ziplistNew(void) {
    // 这里只分配了表头,即 zlbytes,zltail,zllen三个字段记录list的相关信息
    // zlbytes记录整个list的占用空间,zltail记录整个list中最后一个entry的偏移量,zllen记录entry的数量
    unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE;
    unsigned char *zl = zmalloc(bytes);
    ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);
    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);
    ZIPLIST_LENGTH(zl) = 0;
    zl[bytes-1] = ZIP_END;
    return zl;
}

初始分配了一个空的ziplist,只分配了zlbytes、zltai、zllen、,zlend这四个字段。当我们要插入一个节点时,通过ziplistPush方法插入

unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) {
    unsigned char *p;
    p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);
    return __ziplistInsert(zl,p,s,slen);
}
unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, newlen;
    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. */
    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 = zipRawEntryLengthSafe(zl, curlen, ptail);
        }
    }

    /* See if the entry can be encoded */
    if (zipTryEncoding(s,slen,&value,&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. */
    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;
    newlen = curlen+reqlen+nextdiff;
    zl = ziplistResize(zl,newlen);
    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 */
        memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);

        /* 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. */
        assert(zipEntrySafe(zl, newlen, p+reqlen, &tail, 1));
        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 += zipStorePrevEntryLength(p,prevlen);
    p += zipStoreEntryEncoding(p,encoding,slen);
    if (ZIP_IS_STR(encoding)) {
        memcpy(p,s,slen);
    } else {
        zipSaveInteger(p,value,encoding);
    }
    ZIPLIST_INCR_LENGTH(zl,1);
    return zl;
}

我们来分析下这个过程:

  1. 判断当前插入是在头结点插入还是尾结点插入,如果是头结点插入,那么当前指针偏移如下位置#define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t)) 结合上面的结构图,这个应该很明显。如果是尾结点,则偏移#define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1),这里的-1 是插入放在zlend前面。这样就拿到了待插入的节点在链表中的具体位置
  2. 拿到插入的位置后,通过__ziplistInsert进行实际的插入操作。在这里,首先会判断待插入的数据是否全部是数字还是是字符串。如果是数字的话,则会将字符串转换为数字保存,这样能够减少内存空间(并且redis在这里还会判断数字是在哪个范围,继而使用uint_8、uint_16、uint_32、uint_64),如果不是数字的话,则按照实际字符串的长度申请大小。
  3. 这里是节点本身内容的大小,还有节点需要额外记录前驱节点信息,prevlenencoding信息
  4. 对整个链表扩容,并移动数据,移动数据主要是ZIP_END节点,
  5. 写入preLen和encoding信息
  6. 复制数据内容到当前节点

这样我们就在当前list上插入了一个节点。

接下来我们看看,通过一个index获取list中的一个节点ziplistIndex

unsigned char *ziplistIndex(unsigned char *zl, int index) {
    unsigned char *p;
    unsigned int prevlensize, prevlen = 0;
    size_t zlbytes = intrev32ifbe(ZIPLIST_BYTES(zl));

        p = ZIPLIST_ENTRY_HEAD(zl);
        while (index--) {
            /* Use the "safe" length: When we go forward, we need to be careful
             * not to decode an entry header if it's past the ziplist allocation. */
            p += zipRawEntryLengthSafe(zl, zlbytes, p);
            if (p[0] == ZIP_END)
                break;
        }
    
    if (p[0] == ZIP_END || index > 0)
        return NULL;
    zipAssertValidEntry(zl, zlbytes, p);
    return p;
}

可以看到,这里就是通过不断移动指针,来到达指定节点指针,比如我们获取index = 6位置的节点,那么进行5次指针偏移计算,从而到达第6个节点指针。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/555894.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

戴尔 Dell Latitude E7480电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件配置 硬件型号驱动情况 主板戴尔 Dell Latitude E7480 处理器Intel Core i7-7700U已驱动 内存8GB 2133MHz DDR4 * 2已驱动 硬盘Sandisk 1T M.2 NVMe SSD已驱动 显…

windows解决python安装django架构没有django-admin命令

目录 一.尝试安装与配置 1.直接pip命令安装 2.用pycharm测试 3.官网下包安装 二.解决 1.找到django安装的路径 2.配置系统变量 3.测试创建项目 3.1.执行访问页面 3.2.解决 3.3.继续测试 4.pycharm打开 一.尝试安装与配置 1.直接pip命令安装 pip install django dja…

chatgpt赋能Python-python_7

Python 7 - 探索最受欢迎的Python版本 如果你是一位Python开发人员&#xff0c;你就会知道Python 7是什么。Python 7是Python编程语言的第7个主要版本&#xff0c;它带来了许多新的功能和改进&#xff0c;包括内置类型注释、上下文变量、字节码优化和更多。 在本文中&#xf…

度小满科技金融迭代:普惠小微与技术创新并肩而行

2015年&#xff0c;深度贫困乡镇隘口镇推出“本土人才”政策,呼吁大学生人才下基层,助力乡村经济振兴工作。 叶茂的人生轨迹因此而发生改变。彼时&#xff0c;20多岁的叶茂果断辞去药材公司的高薪工作&#xff0c;开始了回乡种植黄精的创业路&#xff0c;并成功竞选东坪村村主任…

log4cpp的安装(vs/visual studio版)

log4cpp安装下载源visual studio安装方法 log4cpp安装 下载源 log4cpp visual studio安装方法 将下载好的压缩包解压缩后得到下列目录&#xff08;解压缩的文件夹最好放C盘&#xff0c;不然在编译阶段往往会出现链接不上的问题&#xff09; 找到mscv10文件夹内的mscv10.sl…

【概念篇】浅谈 AOP、OOP、DDD、IOC

前言 在招聘要求中&#xff0c;有没有经常看见&#xff0c;对AOP、OOP、DDD、IOC有一定的认识&#xff0c;能够自主开发模块&#xff0c;这一类的要求。听起来是不是挺高大上&#xff0c;然后百度一搜&#xff0c;给你出来一堆概念性的东西——结果就是&#xff0c;东西…

《LKD3粗读笔记》(14)块I/O层

什么是块设备&#xff1f; 系统中能够随机访问固定大小数据片的设备被称为块设备&#xff0c;这些数据片称作块&#xff0c;最常见的块设备是硬盘。什么是字符设备&#xff1f; 字符设备按照字符流的方法被有序访问&#xff0c;像串口和键盘就都属于字符设备。这两种设备的区别…

从零开始搭建 Lsky Pro 兰空图床

本文源码&#xff1a;https://github.com/chen2438/chenhaotian.top/tree/main/source/_posts/linux-app/lsky.md 在我的博客上查看&#xff1a;https://chenhaotian.top/2022/12/30/linux-app/lsky/ 从零开始搭建 Lsky Pro 兰空图床 官方教程 安装环境 使用OneinStack安装…

第五章 面向对象-8.enum 枚举

enum 枚举 枚举的实例对象是固定的&#xff0c;实例是自动new&#xff0c;每个枚举类会自动继承java.lang.Enum 抽象类 如何声明枚举&#xff1f; 所有枚举类都是java.lang.Enum的子类&#xff0c;无需用extends来继承&#xff0c;如下是枚举类拥有的常用方法 方法名称说明…

测试报告框架 —— Allure2测试报告

目录 Allure2测试报告 1、使用 Allure2 运行方式-Python 2、使用 Allure2 运行方式-Java 3、生成测试报告 4、Allure2 报告中添加用例标题 5、allure2报告中添加用例步骤 6、allure2报告中添加用例链接 7、allure2报告中添加用例分类 8、Allure2 报告中添加用例描述 …

【面试题】前端必修-浏览器的渲染原理

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 1.浏览器的渲染原理 #浏览器是如何渲染页面的 1.什么叫渲染 render 当我们输入一个url地址的…

新书上市 | 从大脑认知开始,全方面提高编程能力,助你摆脱“GPT焦虑症”

目录 一、ChatGPT火爆全网二、《程序员超强大脑》三、本书内容四、本书特色五、作译者简介1、费莉安赫尔曼斯&#xff08;Felienne Hermans&#xff09;2、蒋楠 大家好&#xff0c;我是哪吒。 &#x1f3c6;本文收录于&#xff0c;49天精通Java从入门到就业。 全网最细Java零…

程序人生-Hello’s P2P

摘要 本文讨论了与编程和软件开发相关的几个关键概念和过程。首先介绍了链接的概念和作用&#xff0c;它是将代码和数据片段组合成单一文件的过程&#xff0c;使得分离编译成为可能&#xff0c;从而可以更好地管理和修改模块。接下来探讨了进程的概念和作用&#xff0c;进程是正…

图像数据处理

文章目录 1&#xff1a;TFRecords1-1 将MNIST数据集转换成TFRecord格式1-2 读取TFRecord文件中的数据 2&#xff1a;图像数据的预处理2-1 处理图像编码2-2 调整图像大小2-3 剪裁和填充2-4 按比例剪裁2-5 图像翻转2-6 图像亮度调整2-7 图像对比度调整2-8 图像色相调整2-9 图像饱…

chatgpt赋能Python-python_99乘法

Python编程实现——99乘法表的生成 Python编程语言是一种高级程序设计语言&#xff0c;具有简单易学、可移植性强、功能强大等特点&#xff0c;受到广大开发者的喜爱。Python可以被应用于网站开发、数据分析、人工智能、机器学习等多个领域。而在Python编程中&#xff0c;生成…

《程序员面试金典(第6版)》面试题 02.06. 回文链表(双指针(快慢指针),查找链表中间节点,反转链表)

题目描述 编写一个函数&#xff0c;检查输入的链表是否是回文的。 题目传送门~&#xff1a;面试题 02.06. 回文链表 示例 1&#xff1a; 输入&#xff1a; 1->2 输出&#xff1a; false 示例 2&#xff1a; 输入&#xff1a; 1->2->2->1 输出&#xff1a; true 进…

蓝桥杯单片机串口通信学习提升笔记——部分2

今日继续学习提升蓝桥杯国赛能力水平。 有道是&#xff1a;卜心事、灯花无语&#xff0c;百感孤单&#xff0c;鸳被羞展...... 梦方圆&#xff0c;又丛钟、声声惊断。 诗人杨玉衔孤单影只&#xff0c;偏偏又多遭磨难&#xff0c;一路坎坷...... 正如我近日来学习提升串口通信…

数据结构学习分享之链式二叉树(一)

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:数据结构学习分享⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你了解更多数据结构的知识   &#x1f51d;&#x1f51d; 1. 前言 在学习链式二叉树…

【Linux】shell编程—awk编辑器

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、awk编辑器1.工作流程3.常用选项 二、awk的基础用法1.输出文件中的某一列2.根据特定条件筛选数据3.按照分隔符进行切割4.在匹配到特定字符串时执行操作5.BEGIN打…

chatgpt赋能Python-pythonwxpy

Python的wxpy模块&#xff1a;一款强大的微信机器人框架 在当今数字时代&#xff0c;微信已经成为了大家日常生活中不可缺少的应用。wxpy是一款使用Python语言的微信机器人框架&#xff0c;可以帮助用户实现诸如自动回复、消息提醒、定时发送消息等自动化操作。它的易用性、强…