Redis中的压缩列表(ZipList)

news2024/11/25 10:28:00

前言

压缩列表的最大特点,就是它是一种内存紧凑型的数据结构,占用一块连续的内存空间,而且还会根据数据类型的不同,选择不同的编码方式来节省内存。

压缩列表的缺点也很明显

  • 它查询节点只能一个一个查,所以时间复杂度是O(n)。不能存放过多的节点,查询效率会变低。
  • 修改/新增数据时,需要重新计算压缩列表空间,并且可能会导致连锁更新问题。

什么是压缩列表

它不同于其他的数据结构,我在Redis甚至看不到它的结构体定义,因为它本身就是一块连续的内存地址。找到它的new函数看看

/* Create a new empty ziplist. */
unsigned char *ziplistNew(void) {
    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的列表头大小,包括2个32 bits整数和1个16bits整数,分别表示压缩列表的总字节数,列表最后一个元素的离列表头的偏移,以及列表中的元素个数
#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))
//ziplist的列表尾大小,包括1个8 bits整数,表示列表结束。
#define ZIPLIST_END_SIZE        (sizeof(uint8_t))
//ziplist的列表尾字节内容
#define ZIP_END 255 

在这里插入图片描述

属性类型长度作用
zlbytesuint32_t4字节记录整个压缩列表大小
zltailuint32_t4字节记录压缩列表尾部偏移量
zllenuint16_t2字节entry个数,最大uint16_max(65534),超过则记录65534。真实需要遍历整个压缩列表才能得到。
entry不定不定保存数据
zlenduint8_t1字节0xFF(255)特殊字符,表示列表结束

Entry定义

在这里插入图片描述

所以整个压缩列表的定义可以是这样

在这里插入图片描述

Entry与普通链表节点的区别

普通链表需要记录两个指针,一个 *pre,一个 *next,这样就需要耗费16个字节,而Entry仅仅记录了上一个节点的字节长度、

prevlen:上一节点长度,1字节或5字节

  • 前一个节点长度小于254字节,那当前prevlen需要用1个字节来保存长度值
  • 前一个节点长度大于254字节,那当前prevlen需要用5个字节来保存长度值

encoding:字符串还是整数,1、2字节或5字节

data:保存真实数据

而为了方便查找,每个列表项中都会记录前一项的长度。因为每个列表项的长度不一样,所以如果使用相同的字节大小来记录 prevlen,就会造成内存空间浪费。

举个例子,假设我们统一使用 4 字节记录 prevlen,如果前一个列表项只是一个字符串“redis”,长度为 5 个字节,那么我们用 1 个字节(8 bits)就能表示 256 字节长度(2 的 8 次方等于 256)的字符串了。此时,prevlen 用 4 字节记录,其中就有 3 字节是浪费掉了。

好,我们再回过头来看,ziplist 在对 prevlen 编码时,会先调用 zipStorePrevEntryLength 函数,用于判断前一个列表项是否小于 254 字节。如果是的话,那么 prevlen 就使用 1 字节表示;否则,zipStorePrevEntryLength 函数就调用 zipStorePrevEntryLengthLarge 函数进一步编码。这部分代码如下所示:

//判断prevlen的长度是否小于ZIP_BIG_PREVLEN,ZIP_BIG_PREVLEN等于254
if (len < ZIP_BIG_PREVLEN) {
   //如果小于254字节,那么返回prevlen为1字节
   p[0] = len;
   return 1;
} else {
   //否则,调用zipStorePrevEntryLengthLarge进行编码
   return zipStorePrevEntryLengthLarge(p,len);
}

也就是说,zipStorePrevEntryLengthLarge 函数会先将 prevlen 的第 1 字节设置为 254,然后使用内存拷贝函数 memcpy,将前一个列表项的长度值拷贝至 prevlen 的第 2 至第 5 字节。最后,zipStorePrevEntryLengthLarge 函数返回 prevlen 的大小,为 5 字节。

if (p != NULL) {
    //将prevlen的第1字节设置为ZIP_BIG_PREVLEN,即254
    p[0] = ZIP_BIG_PREVLEN;
  //将前一个列表项的长度值拷贝至prevlen的第2至第5字节,其中sizeof(len)的值为4
    memcpy(p+1,&len,sizeof(len));}
//返回prevlen的大小,为5字节
return 1+sizeof(len);

encoding 编码

一个列表项的实际数据,既可以是整数也可以是字符串。整数可以是 16、32、64 等字节长度,同时字符串的长度也可以大小不一。

所以,ziplist 在 zipStoreEntryEncoding 函数中,针对整数和字符串,就分别使用了不同字节长度的编码结果。下面的代码展示了 zipStoreEntryEncoding 函数的部分代码,你可以看到当数据是不同长度字符串或是整数时,编码结果的长度 len 大小不同。

//默认编码结果是1字节
  unsigned char len = 1;
  //如果是字符串数据
  if (ZIP_IS_STR(encoding)) {
      //字符串长度小于等于63字节(16进制为0x3f)
        if (rawlen <= 0x3f) {
            //默认编码结果是1字节}
    //字符串长度小于等于16383字节(16进制为0x3fff) 
        else if (rawlen <= 0x3fff) {   
            //编码结果是2字节
            len += 1;}
    //字符串长度大于16383字节
 
        else {
            //编码结果是5字节
            len += 4;}
    } else {
        /* 如果数据是整数,编码结果是1字节*/
        if (!p) return len;
        ...
    }

简而言之,针对不同长度的数据,使用不同大小的元数据信息(prevlen 和 encoding),这种方法可以有效地节省内存开销。

连锁更新问题

在压缩列表的节点中

prevlen:上一节点长度,1字节或5字节

  • 前一个节点长度小于254字节,那当前prevlen需要用1个字节来保存长度值
  • 前一个节点长度大于254字节,那当前prevlen需要用5个字节来保存长度值

举个极端例子,不过可能性也是非常小

在这里插入图片描述

这个地方更新,后一个节点本身长度也已经是254,现在从prevlen从1字节修改成5字节,整个节点又超出254,又需要更新。此时就出现了连锁更新问题。

小结

  • 压缩列表可以看做是特殊的“双向链表”。因为保存了头尾节点偏移地址。
  • 压缩列表不通过指针存储,而是直接采用一块连续的内存地址,并且不同大小的数据采用不同的编码方式,内存占用少。
  • 压缩列表不能存储太多数据,因为它查找数据的速度是O(n)。
  • 压缩列表存在连锁更新问题,只要节点不是特别多,开销是能接受的。

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

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

相关文章

用springboot创建helloworld项目

目录 一、什么是springboot 二、使用idea构建springboot &#xff08;1&#xff09;下载idea &#xff08;2&#xff09;在idea配置maven &#xff08;3&#xff09;利用springboot构建1个helloworld的web项目​编辑​编辑 ​编辑 &#xff08;4&#xff09;启动springboot…

软考A计划-电子商务设计师-电子商务系统的测试

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

Zinx框架学习 - 消息队列及多任务

Zinx - V0.8 消息队列及多任务 之前zinxV0.7我们已经实现了读写分离&#xff0c;对应每个client&#xff0c;我们有3个go程&#xff0c;分别是reader、writer、DoMsgHandle假设服务器有10W个client请求&#xff0c;那么server就会有10W个reader的go、10W个writer的go程&#x…

python编程——环境搭建

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 本文专栏&#xff1a;python专栏 专栏介绍&#xff1a;本专栏为免费专栏&#xff0c;并且会持续更新python基础知识&#xff0c;欢迎各位订阅关注。 目录 一 、安装python 1、进入官网下载python 2、打开安装…

【AI4DB】商用数据库-使用AI4DB技术并商用的数据库总结

目录 1.Amazon Redshift参考链接&#xff1a; 2.阿里云-DAS-Database Autonomy Service参考链接&#xff1a; 3.Oracle Autonomous Database参考链接&#xff1a; 4.阿里云-MaxCompute&#xff08;原ODPS&#xff09;参考文档&#xff1a; 5.腾讯云——DBbrain参考链接&#xf…

python 算符优先分析法的设计实现 编译原理

本文内容&#xff1a; 1、给出文法如下: G[E] E->T|ET; T->F|T*F; F->i|(E); 可以构造算符优先表如下: *()i><<><*>><><(<<<<)>>>i>>> 2、计算机中表示上述优先关系&#xff0c;优先关系的机内存放…

飞桨花滑骨骼点动作识别比赛——从 baseline 调优讲解

赛题介绍背景数据集 思路讲解backbone 模型文件结构 -- PaddleVideo 框架configs 文件夹paddlevideo 文件夹 模型介绍1. ST-GCN -- Baseline 模型整体结构GCN部分TCN部分 2. 2s-AGCN自适应图卷积双流网络 3. CTR-GCNCTR-GC 赛题介绍 背景 2021 CCF BDCI 基于飞桨实现花样滑冰…

初识JavaScript---(1)

初识JavaScript———&#xff08;1&#xff09;&#xff01;&#xff01;&#xff01; 一、初识JavaScript 1.什么是JavaScript&#xff1f; JavaScript是运行在浏览器上的脚本语言&#xff0c;简称JS。JavaScript程序不需要我们程序员手动编译&#xff0c;编写完源代码之后…

shell编程-02-变量作用域

作用域 局部变量&#xff1a;变量只能在函数内部使用 全局变量&#xff1a;变量可以在当前 Shell 进程中使用 环境变量&#xff1a;变量还可以在子进程中使用 局部变量 函数中定义的变量默认是全局变量&#xff0c;在定义时加上local命令&#xff0c;此时该变量就成了局部变…

Spring系列-10 事务机制

背景&#xff1a; 在 事务-1 事务隔离级别和Spring事务传播机制 中对事务的特性、隔离级别、Spring事务的传播机制结合案例进行了分析&#xff1b;在 事务-2 Spring与Mybatis事务实现原理 中对JDBC、Mybatis、Spring整合Mybatis实现事务的原理结合框架源码进行了介绍&#xff…

如何免费使用GPT-4模型

一、引言 OpenAI 最近发布了ChatGPT最新的 GPT-4 模型&#xff0c;这是 OpenAI 迄今为止发布的最强大的语言模型系统。它不仅有视觉能力&#xff0c;而且是多模态的&#xff0c;可以解释文本和生成图像。此外&#xff0c;它在推理测试中表现良好&#xff0c;可以支持大约26种不…

Redis的ZipList和QuickList和SkipList和RedisObject

ZipList:压缩列表&#xff0c;为了节省内存而设计的一种数据结构 ZipList是一种特殊的双端链表&#xff0c;是由一系列的特殊编码的连续内存块组成&#xff0c;不需要通过指针来进行寻址来找到各个节点&#xff0c;可以在任意一端进行压入或者是弹出操作&#xff0c;并且该操作…

RocketMQ的学习历程(5)----broker内部设计

文章目录 概要整体架构流程技术名词解释CommitLog和ConsumeQueue页缓存和内存映射刷盘机制 小结 概要 在首个学习历程中&#xff0c;我们已经了解了&#xff0c;RokctMQ简单的工作流程。 如果想要更深的理解RokcetMQ消息处理的流程&#xff0c;broker内部流程的理解是必要的&…

【挑战全站最全】Linux系统的安装与配置教程——以CentOS为例

&#x1f680;作者&#xff1a;那个叫马尔的大夫&#x1f680; ⭐专栏&#xff1a;操作系统⭐ &#x1f33c;内容&#xff1a;主要分享一些关于Linux操作系统的知识 &#x1f967;不忘初心&#xff0c;砥砺前行~ 目录 一、用到的软件环境——虚拟机软件&#xff08;必需&#…

调用函数不仅仅只是传递正确的参数类型

这里有一个新手犯下的一个典型错误。 假设&#xff0c;我们想调用这个函数&#xff0c;GetBinaryType。 void sample() { if (GetBinaryType(TEXT(“explorer.exe”), ????)) { … } } 请问&#xff0c;这里的问号处应该传递什么类型的参数&#xff1f;你可能会说&#x…

python、pyqt5实现人脸检测、性别和年龄预测

摘要&#xff1a;这篇博文介绍基于opencv&#xff1a;DNN模块自带的残差网络的人脸、性别、年龄识别系统&#xff0c;系统程序由OpenCv, PyQt5的库实现。如图系统可通过摄像头获取实时画面并识别其中的人脸表情&#xff0c;也可以通过读取图片识别&#xff0c;本文提供完整的程…

设计模式入门:策略模式

现有一套模拟鸭子游戏&#xff0c;可以一边游泳&#xff0c;一边呱呱叫。 每种鸭子都会呱呱叫和游泳&#xff0c;只是外观不同。因此&#xff0c;quack和swim放在父类中&#xff0c;display放在子类中实现。 增加新的功能&#xff1a;鸭子飞翔。 1 我们可能想到直接在父类中增…

LeetCode——最小化字符串长度

目录 一、题目 二、题目解读 三、代码 1、set去重 2、用一个二进制数记录每个字母是否出现过 一、题目 6462. 最小化字符串长度 - 力扣&#xff08;Leetcode&#xff09; 给你一个下标从 0 开始的字符串 s &#xff0c;重复执行下述操作 任意 次&#xff1a; 在字符串…

聊一聊数据库事务的那些事(隔离级别,传播行为)

我们平时使用事务的时候&#xff0c;可能脑子里面想到和事务有关的知识点无非就是&#xff0c;ACID&#xff0c;事务隔离级别那一套&#xff0c;使用的事务也就是是通过注解的形式&#xff0c;或者手动开启事务。更细致一点的问题或许没有深究下去&#xff0c;比如事务的传播行…

STM32F407的PWM

文章目录 32的PWM资源PWM输出原理捕获/比较模式寄存器&#xff08;TIMx_CCMR1/2&#xff09;捕获/比较使能寄存器&#xff08;TIMx_CCER&#xff09;捕获/比较寄存器&#xff08;TIMx_CCR1~4&#xff09; 库函数版本的PWM波输出开启 TIM3 时钟以及复用功能时钟置 &#xff0c;配…