Redis基础数据结构之——ZipList

我们在工作、学习中接触Redis时,基本都听说过压缩列表这个数据结构,但是具体它是做什么的?它是如何实现的?并没有多少人深入的了解过,但是…面试官可能会问。

以下内容均基于Redis6.0

一、 什么是ZipList

Redis对内存的使用极其苛刻,当你阅读Redis源码的时候,你就会发现,当我们用同一个数据结构存储数据时,数据量小时可能会是一个内存很紧凑的数据结构,正是因为内存紧凑,所以我们在插入数据时,往往需要重新申请内存,然后将元数据移动到新内存中再进行插入,当数据量大时,这个结构因复制原有数据带来的开销就远远大于其内存上的优势了。

ZipList就是当【zset】和【hash】容器对象在元素个数较少或元素长度较短时采用的数据结构。它是一块连续的内存空间,每一个元素都前后挨着,中间没有内存空隙。同时它也是一个经过特殊编码的双向链表,它的设计目标就是为了提高内存存储效率,

ziplist可以用于存储字符串或整数,其中整数是按真正的二进制表示进行编码的,而不是编码成字符串序列。它能以O(1)的时间复杂度在表的两端提供push和pop操作

Redis使用ZipList的默认条件

  • 键值对数量少于128个
  • 所有键值对中每个元素值的长度【小于或等于】64字节

这里我们只对第二个条件【所有键值对中每个元素值的长度小于或等于64字节】做个演示,键值对数量少于128个可以自己试一试。下图所示,当字节数key为fenglan的这个zset结构插入的value超过64字节时,zset的数据结构就会升级为跳跃表SkipList。

演示1

二、ZipList数据结构

在这里插入图片描述

uint32_t表示一个int类型,占32位,也就是4字节。 uint16_t占16位,只用到了int的低16位,2字节。这样定义比较节省内存空间。

上图就是ZipList的整体数据结构

  • zlbytes:ZipList整体占勇的字节数
  • zltail:最后一个元素的偏移量,用于快速定位到最后一个节点
  • zllen:一共有多少个元素,也就是元素的长度。
  • zlend:ZipList结束标志位,值恒为255

其中zltail这个字段就是为了实现双链表结构才使用的,它可以快速定位最后一个元素,从最后一个元素往前遍历。

Redis创建ZipList的源码如下,我们从源码里能看到,Redis申请了1个uint32_t、1个uint16_t、1个uint8_t的内存空间,正符合我们上面图中除了entry外的其他数据所需的所有内存空间。

在这里插入图片描述

接下来我们一起看看具体的数据存储结构 【entry】

typedef struct zlentry {
    unsigned int prevrawlensize; /* 前一个entry的prevrawlen字段(也就是下面那个字段)的大小*/
    unsigned int prevrawlen;     /* 前一个entry的字节长度 */
    unsigned int lensize;        /* 当前entry的len字段大小*/
    unsigned int len;            /* 当前entrty的字节长度 */
    unsigned int headersize;     /* prevrawlensize + lensize的大小*/
    unsigned char encoding;      /* 元素类型编码*/
    unsigned char *p;            /* 指向具体数据内容首地址的指针*/
} zlentry;

为什么要ZipList要记录前一个entry的长度?答案是因为当ZipList倒着遍历的时候,通过这个字段可以快速定位到下一个元素的位置。

其中prevrawlen是一个变长的int类型数据,当字符串长度小于254时,使用一个字节表示,大于或等于254时
使用5个字节来表示,第一个地接是254,剩余四个字节表示字符串的长度。

三、更新元素
1. 新增元素

因为ZipList是内存紧凑的,所以新插入一个元素就需要扩展内存,Redis会调用ziplistResize方法重新申请内存空间,然后将原先的列表一次性放入新的内存空间中。

2.级联更新

entry中有保存了前一个元素的长度(prevrawlen),它是一个变长的整数,所以如果当前元素的前一个元素发生变化(例如删除),那么,当前元素的prevrawlen字段也会发生改变。当前元素的prevrawlen发生改变,那当前元素下一个元素的prevrawlen也会发生变化,这就触发了大规模的级联更新,当元素很多时,甚至会影响到Redis是否能正常对外提供服务。所以ziplist不适合存放的元素过多。

四、总结
  • Redis 为了节约内存空间才使用的ZipList,zset 和 hash 容器对象在系统默认键值对数量少于512个或所有键值对中每个元素值的长度【小于或等于】64字节,采用压缩列表 (ziplist) 进行存储,否则会升级为SkipList。
  • ZipList是一个双向链表。
  • 压缩列表是一块连续的内存空间,每一个元素都前后挨着,中间没有内存空隙
  • ziplist可以用于存储字符串或整数,其中整数是按真正的二进制表示进行编码的,而不是编码成字符串序列。它能以O(1)的时间复杂度在表的两端提供push和pop操作
  • ZipList的Entry中记录前一个元素的长度是为了双向链表可以倒着遍历。
  • 因为内存非常紧凑,所以新增元素时需要重新申请内存。
  • 可能在元素变更时,会发生级联更新。

原创不易,给个三连吧!!

微信搜一搜:云下风澜
学习、面试资料、深度内容、源码阅读、性能优化、行业秘闻、职业规划应有尽有。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云下牧羊人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值