压缩列表
是列表建和哈希键底层实现之一。
一个列表键只包含少量列表项,每个列表项不是小整数值就是比较短的字符串,此时Redis就会使用压缩列表来做列表键的底层实现。
当一个哈希键值包含少量键值对,每个键值对的键和值要么就是小整数值,要么就是长度比较短的字符串,则Redis就会使用压缩列表做哈希键的底层实现。
哈希键里包含的所有键和值都是小整数值或者短字符串。
1. 压缩列表的构成
压缩列表是Redis为了节约内存而开发的,由一些列特殊编码的连续内存块组成的顺序型的数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。
2.压缩列表节点的构成
每个压缩列表节点可以保存一个字节数组或者一个整数值。
字节数组的种类:
- 长度小于等于63 字节的字节数组。
- 长度小于等于16383字节的字节数组。
- 长度小于等于4294967295字节的字节数组。
整数值的种类:
- 4位长,介于0至12之间的无符号整数。
- 1字节长的有符号整数。
- 3字节长的有符号整数。
- int16_t类型整数。
- int32_t类型整数。
- int64_t类型整数。
每个节点都由previous_entry_length、encoding、content三个部分组成。
2.1 previous_entry_length
以字节为单位,记录表中前一个节点的长度。长度可以是1字节或者5字节。
- 如果前一个节点长度小于254字节,则长度为1字节。前一节点的长度就保存在这一个字节里面。
- 如果前一个节点长度大于254字节,则长度为5字节。属性的第一个字节设置为0xFE(十进制为254),之后的四个字节则保存前一节点的长度。
因为节点的previous_entry_length属性记录了前一个节点的长度,则可以根据当前节点的起始地址,计算出前一个节点的起始地址。
只要知道一个指向某个节点起始地址的指针,则可以通过这个指针以及这个节点的previous_entry_length属性,程序可以一直向前,最终找到列表的表头节点。
示例:查找头结点的过程
2.2 encoding
节点的encoding属性记录了节点content属性所保存数据的类型以及长度。
(1)一字节、两字节或者五字节长,值的最高位为00、01或者10的是字节数组编码,表示节点content属性保存着字节数组,数组长度由编码除去最高两位之后的其他位记录。
(2)一字节长,值的最高位以11开头的是整数编码,编码表示节点的content属性保存着整数值,整数值的类型和长度由编码除去最高两位之后的其他记录。
2.3 content
节点的content属性负责保存节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定。
3. 连锁更新
示例:
在一个压缩列表中,有多个连续、长度介于250字节到253字节之间的节点,在一个长度大于254的新节点new设置为压缩列表的表头节点,则t1保存这个new节点的previous_entry_length的长度则由1字节变为5字节,则t1节点的长度变为254到257之间,则保存t1长度的t2节点长度也需要修改,依次引起的压缩列表的空间的重新分配,直到最后一个节点为止。
在这种特殊情况下产生的连续多次空间扩展操作称之为"连锁更新”(cascade update)。
新增节点,引发的连锁更新:
**注:**尽管连锁更新的复杂度比较高,但是真正造成性能问题的几率很低。
- 压缩表里恰好有多个连续的、长度介于250到253字节之间的点
- 如果出现连锁更新,只要更新的节点数量不多,就不会对性能产生任何影响。
则,ziplistPush等命令的平均时间复杂度为O(N)。
4. 压缩列表 API
5. 回顾
- 压缩列表是一种节约内存而开发的 顺序型数据结构。
- 可被用作列表键和哈希键的底层实现之一。
- 可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
- 添加节点或者删除节点,可能会引发连锁更新操作。