什么是压缩列表
压缩列表是Redis为了节约内存而开发的, 由一系列特殊编码的连续内存块组成的顺序型数据结构。
一个压缩列表可以包含任意多个节点, 每个节点可以保存一个字节数组或者一个整数值。
zlbytes记录整个压缩列表占用的内存字节数
zltial记录压缩列表表尾节点距离压缩列表的起始地址有多少字节
zzlen记录了压缩列表包含的节点数量: 当这个属性的值小于 UINT16_MAX (65535)时, 这个属性的值就是压缩列表包含节点的数量; 当这个值等于 UINT16_MAX 时, 节点的真实数量需要遍历整个压缩列表才能计算得出。
entry压缩列表包含的各个节点,节点的长度由节点保存的内容决定。
zlend特殊值 0xFF (十进制 255 ),用于标记压缩列表的末端。
下面展示一个压缩列表的例子:
- 列表
zlbytes属性的值为0x50(十进制80), 表示压缩列表的总长为80字节。 - 列表
zltail属性的值为0x3c(十进制60), 这表示如果我们有一个指向压缩列表起始地址的指针p, 那么只要用指针p加上偏移量60, 就可以计算出表尾节点entry3的地址。 - 列表
zllen属性的值为0x3(十进制3), 表示压缩列表包含三个节点。
压缩列表的节点
每个压缩列表节点都由 previous_entry_length 、 encoding 、 content 三个部分组成
previous_entry_length
节点的 previous_entry_length 属性以字节为单位, 记录了压缩列表中前一个节点的长度。
其长度是1字节或者5字节:
- 如果前一节点的长度小于
254字节, 那么previous_entry_length属性的长度为1字节: 前一节点的长度就保存在这一个字节里面。 - 如果前一节点的长度大于等于
254字节, 那么previous_entry_length属性的长度为5字节: 其中属性的第一字节会被设置为0xFE(十进制值254), 而之后的四个字节则用于保存前一节点的长度。
比如,前一个节点的长度为5字节
前一个节点的长度为10086字节
因为这个属性,程序可以通过指针运算,根据当前的起始地址来计算出前一个节点的起始地址。 因此压缩列表可以从表头向表尾进行遍历。
下面举一个例子:
- 首先,我们拥有指向压缩列表表尾节点
entry4起始地址的指针p1(指向表尾节点的指针可以通过指向压缩列表起始地址的指针加上zltail属性的值得出); - 通过用
p1减去entry4节点previous_entry_length属性的值, 我们得到一个指向entry4前一节点entry3起始地址的指针p2; - 通过用
p2减去entry3节点previous_entry_length属性的值, 我们得到一个指向entry3前一节点entry2起始地址的指针p3; - 通过用
p3减去entry2节点previous_entry_length属性的值, 我们得到一个指向entry2前一节点entry1起始地址的指针p4,entry1为压缩列表的表头节点;
encoding
节点的 encoding 属性记录了节点的 content 属性所保存数据的类型以及长度:
- 一字节、两字节或者五字节长, 值的最高位为
00、01或者10的是字节数组编码: 这种编码表示节点的content属性保存着字节数组, 数组的长度由编码除去最高两位之后的其他位记录; - 一字节长, 值的最高位以
11开头的是整数编码: 这种编码表示节点的content属性保存着整数值, 整数值的类型和长度由编码除去最高两位之后的其他位记录;
content
节点的 content 属性负责保存节点的值, 节点值可以是一个字节数组或者整数, 值的类型和长度由节点的 encoding 属性决定。
连锁更新
因为每一个压缩列表的节点都保存着前一个节点的长度,并且这个长度有两种可能:1字节或者5字节。
那么考虑这种情况,假设现在所有的压缩列表的节点的长度都是251,这时,所有的的节点的previous_entry_length长度都是1字节。
此时插入一个长度大于等于254字节的新节点在列表的头部,那么第二个节点的previous_entry_length将从1字节变成5字节,第二个节点的整体长度变成255字节,这样第三个节点的previous_entry_length也会从1字节变成5字节,整体长度也会变成255字节,这样就会产生连锁更新,直到最后的节点。
同时,删除一个节点也可能会产生连锁更新。