当前位置: 首页 > news >正文

(ZipList入门笔记二)为何ZipList可以实现内存压缩,可以详细介绍一下吗

文章目录

      • 1. 根除指针:最大的开销来源
      • 2. 变长编码:按需分配,杜绝浪费
      • 3. 紧凑的整体布局
      • 综合示例:一个直观的对比
      • Ziplist的代价:连锁更新

我们来深入探讨一下 Ziplist(压缩列表)实现内存压缩的精髓。

理解 Ziplist 为何能极致地节省内存,关键在于理解它解决了传统数据结构(尤其是双向链表)的哪些“内存浪费”问题

它的压缩魔法主要来源于以下三个核心设计:

1. 根除指针:最大的开销来源

这是 Ziplist 最重要、最根本的内存优化点。

传统双向链表的问题:
一个标准的双向链表,每个节点(Node)除了存储数据外,还必须包含两个指针:一个 next 指向后继节点,一个 prev 指向前驱节点。

在 64 位操作系统中,一个指针就占用 8 个字节。这意味着,每个节点都有 8 + 8 = 16 字节的固定指针开销

想象一下,如果你要存储的数据只是一个很小的整数(例如数字 5,实际只需要 1 字节),那么为了存储这 1 字节的数据,你就要付出 16 字节的指针开销。这就像用一个巨大的集装箱去运送一粒米,内存利用率极低。

Ziplist 的解决方案:
Ziplist 将所有元素存储在一块连续的内存中,从根本上就不需要 nextprev 指针。

  • 如何向后遍历? 因为内存是连续的,知道当前节点的起始地址和它的总长度,就可以计算出下一个节点的起始地址。
  • 如何向前遍历?(这是精髓) Ziplist 的每个节点(entry)内部都包含一个 previous_entry_length 字段,它记录了前一个节点的长度。当需要向前遍历时,用当前节点的起始地址减去这个长度,就能精确地定位到前一个节点的起始位置。这个 previous_entry_length 字段本身也是变长的(1或5字节),远比一个8字节的指针要小。

效果对比:

  • 双向链表N 个节点就有 N * 16 字节的指针开销。
  • Ziplist0 字节的指针开销。

2. 变长编码:按需分配,杜绝浪费

传统结构的问题:
在 C 语言等静态语言中,数据类型通常有固定的大小。例如,int 类型可能固定为 4 字节。如果你要存储数字 10,它实际上只需要 1 个字节,但系统依然会为其分配 4 字节,剩下的 3 字节就被浪费了。

Ziplist 的解决方案:
Ziplist 节点的 encoding 字段可以非常灵活地定义数据的存储方式,真正做到“用多少,给多少”。

  • 对于整数:
    • 如果整数值可以用 1 个字节表示(比如 0-255),encoding 就会指明这是一个单字节整数,content 部分就只占用 1 字节。
    • 如果需要 2 个字节,content 就占用 2 字节。
    • …以此类推,支持多种长度的整数。
  • 对于字符串:
    • encoding 字段会记录字符串的长度,然后 content 部分就紧跟相应长度的字节数组。

这种设计避免了为了容纳“可能的最大值”而预先分配固定大小的空间,而是根据“实际值”来动态决定使用多大的空间。

3. 紧凑的整体布局

除了上述两点,Ziplist 还有一个整体上的优势:

  • 单一的头部信息: 整个 Ziplist 只有一个 zlbytes, zltail, zllen 的头部(总共 10 字节)。而双向链表的“头部”(即指针)是分散在每一个节点里的。
  • 连续内存: 连续的内存布局减少了内存碎片,并且有利于 CPU 的缓存(Cache),在遍历时能获得更好的性能。

综合示例:一个直观的对比

假设我们要在 64 位系统上存储一个列表:["hello", 99]
在这里插入图片描述

  • 双向链表实现:

    • 节点1 (“hello”): 16字节(指针) + 6字节(数据"hello\0") ≈ 22字节
    • 节点2 (99): 16字节(指针) + 8字节(假设用long存储) = 24字节
    • 总计 ≈ 46 字节
  • Ziplist 实现:

  • 在这里插入图片描述

    • 头部: 10字节 (zlbytes+zltail+zllen)
    • 节点1 (“hello”): 1字节(prev_len) + 1字节(encoding) + 5字节(数据) = 7字节
    • 节点2 (99): 1字节(prev_len) + 1字节(encoding) + 1字节(数据) = 3字节
    • 结尾: 1字节 (zlend)
    • 总计 = 10 + 7 + 3 + 1 = 21 字节

可以看到,对于小数据量的场景,Ziplist 的内存压缩效果非常显著。

Ziplist的代价:连锁更新

这种极致的压缩是有代价的。最主要的问题就是连锁更新(Cascading Update)。由于每个节点记录的是前一个节点的长度,当在列表的某个位置插入一个新节点,或者删除了一个节点,可能会导致其后续所有节点previous_entry_length 字段需要更新,这种更新可能会像多米诺骨牌一样传播下去,导致一次插入/删除操作的时间复杂度在最坏情况下达到 O(N²),性能较差。

正因为如此,Ziplist 只适用于存储元素数量较少、内容较小的场景(List, Hash, Zset),并且在后来的 Redis 版本中,逐渐被性能更稳定的 Listpack 所取代。

http://www.lryc.cn/news/610790.html

相关文章:

  • 第19章 枚举器和迭代器 笔记
  • Spring小细节
  • MySQL连接解决:“Host is not allowed to connect to this MySQL server”错误详解
  • HTML总结全览
  • 解决错误nvcc fatal : Unsupported gpu architecture ‘compute_86‘
  • ESOP-3D系统实现机械设备生产流程的可追溯性
  • 人工智能领域、图欧科技、IMYAI智能助手2025年5月更新月报
  • 树状数组的性质
  • AI 对话高效输入指令攻略(四):AI+Apache ECharts:生成各种专业图表
  • C++ ---》string类的模拟实现
  • Solidity智能合约基础
  • 单目云台双摄像头配置双摄像头的优势
  • 深入理解 Android SO 导出符号:机制与安全优化
  • Spring 的优势
  • 应急响应排查思路
  • 市场与销售协同:CRM如何打破部门数据孤岛?
  • 8.5 CSS3多列布局
  • 深入解析RNN神经网络原理与应用
  • GitCode新手使用教程
  • RabbitMQ面试精讲 Day 11:RabbitMQ集群架构与节点类型
  • 人工智能之数学基础:利用全概率公式如何将复杂事件转为简单事件
  • 大模型|极简说清“数据并行”
  • AcWing 3690:求交点 ← 复旦大学考研机试题 + 克莱姆法则
  • 嵌入式开发学习———Linux环境下IO进程线程学习(四)
  • Python爬虫09_Requests用bs4进行数据解析
  • selenium自动化收集资料
  • linux服务器上word转pdf后乱码问题
  • In-memory不要全加载怎么做?
  • 基于LDA主题的网络舆情与情感分析——以云南某景区话题为例
  • 本机部署K8S集群