当存储整数时,第 1 个字节的前 2 位固定为 11,其他位则用来记录整数值的类型或者整数值(下表所示的编码中前两位均为 11):
编码 长度 entry保存的数据11000000 1字节 int16_t类型整数
11010000 1字节 int32_t类型整数
11100000 1字节 int64_t类型整数
11110000 1字节 24位有符号整数
11111110 1字节 8位有符号整数
1111xxxx 1字节 xxxx 代表区间 0001-1101,存储了一个介于 0-12 之间的整数,此时 entry-data 属性被省略
注意:xxxx 四位编码范围是 0000-1111,但是 0000,1111 和 1110 已经被表格中前面表示的数据类型占用了,所以实际上的范围是 0001-1101,此时能保存数据 1-13,再减去 1 之后范围就是 0-12。至于为什么要减去 1 是从使用习惯来说 0 是一个非常常用的数据,所以才会选择统一减去 1 来存储一个 0-12 的区间而不是直接存储 1-13 的区间。
当存储字节数组时,第 1 个字节的前 2 位为 00、01 或者 10,其他位则用来记录字节数组的长度:
编码 长度 entry保存的数据00pppppp 1字节 长度小于等于 63 字节(6 位)的字节数组
01pppppp qqqqqqqq 2字节 长度小于等于 16383 字节(14 位)的字节数组
10000000 qqqqqqqq rrrrrrrr ssssssss tttttttt 5字节 长度小于等于 2 的 32 次方减 1 (32 位)的字节数组,其中第 1 个字节的后 6 位设置为 0,暂时没有用到,后面的 32 位(4 个字节)存储了数据
entry-data
entry-data 存储的是具体数据。当存储小整数(0-12)时,因为 encoding 就是数据本身,此时 entry-data 部分会被省略,省略了 entry-data 部分之后的 ziplist 中的 entry 结构如下:
<prevlen> <encoding>压缩列表中 entry 的数据结构定义如下(源码 ziplist.c 文件内),当然实际存储并没有直接使用到这个结构定义,这个结构只是用来接收数据,所以大家了解一下就可以了:
typedef struct zlentry { unsigned int prevrawlensize;//存储prevrawlen所占用的字节数 unsigned int prevrawlen;//存储上一个链表节点需要的字节数 unsigned int lensize;//存储len所占用的字节数 unsigned int len;//存储链表当前节点的字节数 unsigned int headersize;//当前链表节点的头部大小(prevrawlensize + lensize)即非数据域的大小 unsigned char encoding;//编码方式 unsigned char *p;//指向当前节点的起始位置(因为列表内的数据也是一个字符串对象) } zlentry; ziplist 数据示例上面讲解了大半天,可能大家都觉得枯燥无味了,也可能会觉得云里雾里,这个没有关系,这些只要心里有个概念,用到的时候再查询对应资料就可以了,并不需要全部记住,接下来让我们一起通过两个例子来体会一下 ziplist 到底是如何来组织存储数据的。
下面就是一个压缩列表的存储示例,这个压缩列表里面存储了 2 个节点,节点中存储的是整数 2 和 5:
[0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff] | | | | | | zlbytes zltail zllen "2" "5" end第一组 4 个字节为 zlbytes 部分,0f 转成二进制就是 1111 也就是 15,代表整个 ziplist 长度是 15 个字节。
第二组 4 个字节 zltail 部分,0c 转成二进制就是 1100 也就是 12,这里记录的是压缩列表尾节点距离起始地址有多少个字节,也就是就是说 [02 f6] 这个尾节点距离起始位置有 12 个字节。
第三组 2 个字节就是记录了当前 ziplist 中 entry 的数量,02 转成二进制就是 10,也就是说当前 ziplist 有 2 个节点。
第四组 2 个字节 [00 f3] 就是第一个 entry,00 表示 0,因为这是第 1 个节点,所以前一个节点长度为 0,f3 转成二进制就是 11110011,刚好对应了表格中的编码 1111xxxx,所以后面四位就是存储了一个 0-12位的整数。0011 转成十进制就是 3,减去 1 得到 2,所以第一个 entry 存储的数据就是 2。