压缩列表(ziplist)是列表键和哈希键的底层实现之一。
当列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么是短字符串,Redis就会用压缩列表作为列表键的底层实现
Redis的其他数据结构相关如下:
简单动态字符串
链表
字典
整数集合
压缩列表是Redis为了节约内存开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。
下面是各个部分的详细说明:
| 属性 | 类型 | 长度 | 用途 |
|---|---|---|---|
| zlbytes | uint32_t | 4字节 | 记录整个压缩列表占用的内存字数:在对压缩列表进行内存重分配或者计算zlend的位置时使用 |
| zltail | uint32_t | 4字节 | 记录压缩列表表尾节点距离压缩列表的起始地址有多少字节:通过这个偏移量,程序无需遍历整个压缩列表就可以确定表尾节点的地址 |
| zllen | uint16_t | 2字节 | 记录了压缩列表包含的节点数量:当这个属性的值小于UINT16_MAX(65535)时,这个属性的值就是压缩列表包含节点的数量;当这个值等于UINT16_MAX,节点的真是数量需要遍历整个压缩列表后才能计算得出 |
| entryX | 列表节点 | 不定 | 压缩列表包含的各个节点,节点的长度由节点保存的内容决定 |
| zlend | uint8_t | 1字节 | 特殊值0xFF(十进制255),用于标记压缩列表的末端 |
以下面的压缩列表为例

zlbytes:该值是0x50(十进制的80),表示压缩列表的总长度位80字节zltail:该值是0x3c(十进制的60),假如此时有一个指针p指向压缩列表起始地址,那么用该指针加上偏移量60就可以计算出表尾节点entry3的地址zllen:该值是0x3(十进制的3),表示该压缩列表包含三个节点每个压缩列表节点都可以保存一个字节数组或者一个整数值
如果是字节数组:
如果是整数值的话:
int16_t类型整数int32_t类型整数int64_t类型整数每个列表节点的结构示意图如下:

节点的previous_entry_length属性以字节为单位,记录了前一个节点的长度,该属性长度可以是一个字节也可以是五个字节。
previous_entry_length属性的长度为1字节;前一字节的长度就保存在这一个字节里面previous_entry_length属性的长度为5字节;其中属性的第一字节就会被设置为0xFE(十进制254),而之后的四个字节则用于保存前一节点的长度示例

0xFE表示这是一个五字节长的previous_entry_length属性,而之后的0x00002766(十进制10086)才是前一节点的长度好处
因为previous_entry_length记录了前一节点的长度,所以只要拿到当前节点的地址,就能实现从表尾节点向表头方向进行遍历操作(用当前指针减去previous_entry_length属性的值就能获得上一节点的起始地址)
上面提及到,压缩列表节点可以保存字节数组或者整数,encoding属性就是用来表示当前节点的存储数据类型和长度
content属性存储的是字节数组。数组的长度由encoding出去最高两位之后的其他位记录content属性存储的是整数值,整数值的类型和长度由encoding出去最高两位之后的其他位记录。
节点的content属性负责保存节点值,节点值可以是一个字节数组或者整数,值得类型和长度由encoding属性决定。
图7-9是一个存储字节数组的节点
content表示该节点存储的是hello world11000000表示节点保存的是一个int16_t类型的整数值content属性表示保存的是10086回忆上面每个节点的结构中,都有一个previous_entry_length用于存储前一个节点的长度。
如果前一节点的长度小于254,则该节点的previous_entry_length用一个字节表示,反之需要五个字节。
考虑这样的一种极端情况:
previous_entry_length需要由原先的一个字节扩展为五个字节previous_entry_length扩展会导致e1节点长度介于254-257之间,这会导致e1的后续节点也引发相应的previous_entry_length扩展,并且后续的所有节点都会发生这种问题previous_entry_length属性都符合压缩列表对于节点的要求,程序需要不断地对压缩列表执行空间重分配实际上连锁更新发生的条件并不常见;并且即使出现连锁更新,只要被更新的节点数量不多,就不会导致任何影响。