一、概念
压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项, 并且每个列表项要么就是小整数值, 要么就是长度比较短的字符串, 那么 Redis 就会使用压缩列表来做列表键的底层实现。比如说, 执行以下命令将 创建一个压缩列表实现的列表键:
因为哈希键里面包含的所有键和值都是小整数值或者短字符串。
二、压缩列表的特点
1. 压缩列表是一种为节约内存而开发的顺序型数据结构。
2. 压缩列表被用作列表键和哈希键的底层实现之一。
3. 压缩列表可以包含多个节点,每个节点可以包含一个字节数组或者整数值。
4. 添加新节点到压缩列表,或者从压缩列表中删除列表节点,可能会引发连锁更新操作,但这种操作出现的几率并不高。
三、压缩列表的构成
压缩列表是 Redis 为了节约内存而开发的, 由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构。
一个压缩列表可以包含任意多个节点(entry), 每个节点可以保存一个字节数组或者一个整数值。下图展示了压缩列表的各个组成部分。
下图展示一个压缩列表示例:
1. 列表 zlbytes 属性的值为 0x50 (十进制 80), 表示压缩列表的总长为 80 字节。
2. 列表 zltail 属性的值为 0x3c (十进制 60), 这表示如果我们有一个指向压缩列表起始地址的指针 p , 那么只要用指针 p 加上偏移量 60 , 就可以计算出表尾节点 entry3 的地址。
3. 列表 zllen 属性的值为 0x3 (十进制 3), 表示压缩列表包含三个节点。
四、压缩列表节点的构成
每个压缩列表节点可以保存一个字节数组或者一个整数值, 其中, 字节数组可以是以下三种长度的其中一种:
1. 长度小于等于 63 (2^{6}-1)字节的字节数组;
2. 长度小于等于 16383 (2^{14}-1) 字节的字节数组;
3. 长度小于等于 4294967295 (2^{32}-1)字节的字节数组;
而整数值则可以是以下六种长度的其中一种:
1. 4 位长,介于 0 至 12 之间的无符号整数;
2. 1 字节长的有符号整数;
3. 3 字节长的有符号整数;
4. int16_t 类型整数;
5. int32_t 类型整数;
6. int64_t 类型整数。
每个压缩列表节点都由 previous_entry_length 、 encoding 、 content 三个部分组成,如图所示:
1. previous_entry_length
节点的 previous_entry_length 属性以字节为单位, 记录了压缩列表中前一个节点的长度。
previous_entry_length 属性的长度可以是 1 字节或者 5 字节:
1). 如果前一节点的长度小于 254 字节, 那么 previous_entry_length 属性的长度为 1 字节: 前一节点的长度就保存在这一个字节里面。
2). 如果前一节点的长度大于等于 254 字节, 那么 previous_entry_length 属性的长度为 5 字节: 其中属性的第一字节会被设置为 0xFE(十进制值 254), 而之后的四个字节则用于保存前一节点的长度。
下图展示了一个包含一字节长 previous_entry_length 属性的压缩列表节点, 属性的值为 0x05 , 表示前一节点的长度为 5 字节。
展示了一个包含五字节长 previous_entry_length 属性的压缩节点, 属性的值为 0xFE00002766 , 其中值的最高位字节 0xFE 表示这是一个五字节长的 previous_entry_length 属性, 而之后的四字节 0x00002766 (十进制值 10086 )才是前一节点的实际长度。
因为节点的 previous_entry_length 属性记录了前一个节点的长度, 所以程序可以通过指针运算, 根据当前节点的起始地址来计算出前一个节点的起始地址。
举个例子, 如果我们有一个指向当前节点起始地址的指针 c , 那么我们只要用指针 c 减去当前节点 previous_entry_length 属性的值, 就可以得出一个指向前一个节点起始地址的指针 p。
压缩列表的从表尾向表头遍历操作就是使用这一原理实现的: 只要我们拥有了一个指向某个节点起始地址的指针, 那么通过这个指针以及这个节点的 previous_entry_length 属性, 程序就可以一直向前一个节点回溯, 最终到达压缩列表的表头节点。
2. encoding
节点的 encoding 属性记录了节点的 content 属性所保存数据的类型以及长度:
1. 一字节、两字节或者五字节长, 值的最高位为 00 、 01 或者 10 的是字节数组编码: 这种编码表示节点的 content 属性保存着字节数组, 数组的长度由编码除去最高两位之后的其他位记录;
2. 一字节长, 值的最高位以 11 开头的是整数编码: 这种编码表示节点的 content 属性保存着整数值, 整数值的类型和长度由编码除去最高两位之后的其他位记录;
下面两个表分别记录了所有可用的字节数组编码和所有可用的证数编码。表格中的下划线_表示留空,而b、x等变量则代表实际的二进制数据,为了方便阅读,多个字节之间用空格隔开。
2. content
节点的 content 属性负责保存节点的值, 节点值可以是一个字节数组或者整数, 值的类型和长度由节点的 encoding 属性决定。
下图展示了一个保存字节数组的节点示例:
1). 编码的最高两位 00 表示节点保存的是一个字节数组;
2). 编码的后六位 001011 记录了字节数组的长度 11 ;
3). content 属性保存着节点的值 "hello world" 。
下图展示了一个保存整数值的节点示例:
1). 编码 11000000 表示节点保存的是一个 int16_t 类型的整数值;
2). content 属性保存着节点的值 10086 。
2019-08-18