压缩列表是怎么实现的?
压缩列表结构图:

链表表头有三个字段:
zlbytes:用于统计整个压缩列表有多少字节
zltail:用于统计末尾节点距离起始节点相差多少字节的距离
zllen:用于统计整个列表的节点数量
末尾一个字段:
zlend:用于标记列表的结束点,固定值是 0xFF(十进制是 255)
如果需要查询列表的最后一个节点,只需要依赖头部字段 zltail 就可以查到了,只需要查找一次,复杂度是 O(1)。而其他节点就需要依次查找,时间复杂度是 O(n),所以压缩列表不适合存储元素过多的场景。
每一个节点有三个属性:
prevlen:存放前一个节点占用的内存字节大小,列表倒序就需要依赖 prevlen
encoding:存放当前数据的类型,整数型或者字符串
data:存当前数据
当压缩列表插入数据时,会根据当前元素的类型和大小来使用不同的空间分配方案,这一根据元素大小分配空间的方案提高了内存的使用率。
压缩列表的缺点:
一旦遇到连锁更新时,就有可能导致压缩列表分配的内存空间重新分配,而多次的重新分配会影响压缩列表的访问性能
若节点过多或节点过大也有可能引发连锁更新,所以压缩列表适合存储节点比较少的场景。Redis 为了解决这个缺点,在 Redis 3.2 引入了 quicklist,在 Redis 5.2 引入了 listpack,保存了节省空间的优点,同时解决压缩列表连锁更新的问题。
介绍一下 Redis 中的 Listpack
Listpack 核心目的就是解决了 Ziplist(压缩列表)的连锁更新问题。
Listpack 的结构如下:

头部两个属性:记录了总字节数和元素数量
结尾还有结尾标识
每一个元素有三个属性:
encoding:定义该元素的编码类型,会对整数或字符串进行不同的编码
data:数据
len:记录 encoding 和 data 的长度
Listpack 每一个节点会根据内容的大小来进行不同的编码,主要依赖于 encoding。没有了 prevlen 之后依旧可以倒叙,因为如果更改字符串不会导致其他节点的内容变化,直接解决了连锁更新的问题。
倒序遍历的核心逻辑:
- 第一步:通过表头的「总字节数」定位到整个 Listpack 的末尾。
- 第二步:减去结尾标识(1 字节),得到最后一个节点的末尾地址。
- 第三步:读取该节点的
len字段,用末尾地址减去len得到上一个节点的末尾地址。 - 第四步:重复第三步,直到遍历到表头,完成倒序。
哈希表是怎么扩容的?
哈希扩容需要两个哈希表:



