1,当一个列表只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做底层实现结构。
2,压缩列表是Redis为了节约内存而开发的,**是由一系列特殊编码的连续内存块组成的顺序型数据结构。**一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。
3,压缩列表各部分组成如下:
zlbytes(4字节):记录整个压缩列表占用的内存字节数:在对压缩列表进行内存重分配,或者计算zlend的位置时使用
zltail(4字节):记录压缩列表表尾节点距离压缩列表的起始地址有多少字节:通过这个便宜量,程序无须遍历整个压缩列表就可以确定表尾节点的地址
zllen(2字节):记录了压缩列表包含的节点数量,当这个值小于INT_MAX时,这个值就是压缩列表包含节点的数量。当这个值等于INT_MAX时,节点的真实数量需要遍历整个压缩列表才能计算得出。
entryX:列表节点,节点的长度由节点保存的内容决定
zlend(1字节):特殊值0xFF,用于标记压缩列表的末端。C语言相当于字符串的\n
4,压缩列表的节点构成
每个压缩列表节点可以保存一个字节数组或者一个整数值,每个压缩列表节点都由previous_entry_length、encoding、content三个部分组成。
1)previous_entry_length
记录了前一个节点的长度,这个字段的长度可以是1个字节也可以是5个字节。
如果前一个字节的长度大小是254,那么previous_entry_length属性的长度为1字节,前一字节的长度就保存在这个字节中;如果前一个字节的长度大于254,那么previous_entry_length属性的长度为5字节,第一字节设置为0xFE,而之后的4个字节用于保存前一个字节的长度。只要我们知道了这个节点的指针,然后减去previous_entry_length的值就可以回溯到前一个节点的地址。
2)encoding
节点的encoding属性记录了节点的content所保存数据的类型以及长度。
前两位是00是1字节长的数组编码,content长度小于等于63字节的字节数组
前两位是01是2字节长的数组编码,content长度小于等于16383字节的字节数组
前两位是10是5字节长的数组编码,content长度小于等于4294967295字节的字节数组
前两位是11是1字节长的整数编码
如果是字节数组后面的位表示的是长度。
3)content
节点的content属性负责保存节点的值,值的属性和长度由节点的encoding属性决定。
5,需要注意的是压缩列表可能由于增加或删除压缩列表节点存在连锁更新的情况,而每次连锁更新的时间复杂度是O(N*N)。虽然时间复杂度很高,但是它真正造成性能问题的几率是很低的。原因有两个:
1)压缩列表里要恰好有多个连续的、长度介于250~253字节之间的节点连锁更新才有可能被引发,但是实际这种情况并不多见
2)即使出现连锁更新,但只要被更新的节点数量不多,就不会对性能造成任何影响。
6,增删节点的平均时间复杂度是O(N)