合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
`ziplist`的`head`和`end`存的都是长度和标记,而`entry`存储的是具体元素,这又是经过特殊设计的一种存储格式,每个`entry`都以包含两段信息的元数据作为前缀,每一个`entry`的组成结构为: ~~~properties <prevlen> <encoding> <entry-data> ~~~ #### prevlen `prevlen`属性存储了前一个`entry`的长度,通过此属性能够从后到前遍历列表。`prevlen`属性的长度可能是`1`字节也可能是`5`字节: * 当链表的前一个`entry`占用字节数小于`254`,此时`prevlen`只用`1`个字节进行表示。 ~~~c <prevlen from 0 to 253> <encoding> <entry> ~~~ * 当链表的前一个`entry`占用字节数大于等于`254`,此时`prevlen`用`5`个字节来表示,其中第`1`个字节的值固定是`254`(相当于是一个标记,代表后面跟了一个更大的值),后面`4`个字节才是真正存储前一个`entry`的占用字节数。 ~~~c 0xFE <4 bytes unsigned little endian prevlen> <encoding> <entry> ~~~ 注意:`1`个字节你完全能存储`255`的大小,之所以只取到`254`是因为`zlend`就是固定的`255`,所以`255`这个数要用来判断是否是`ziplist`的结尾。 #### encoding `encoding`属性存储了当前`entry`所保存数据的类型以及长度。`encoding`长度为`1`字节,`2`字节或者`5`字节长。前面我们提到,每一个`entry`中可以保存字节数组和整数,而`encoding`属性的第`1`个字节就是用来确定当前`entry`存储的是整数还是字节数组。当存储整数时,第`1`个字节的前两位总是`11`,而存储字节数组时,则可能是`00`、`01`和`10`三种中的一种。 * 当存储整数时,第`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`结构如下: ~~~properties <prevlen> <encoding> ~~~ 压缩列表中`entry`的数据结构定义如下(源码`ziplist.c`文件内),当然实际存储并没有直接使用到这个结构定义,这个结构只是用来接收数据,所以大家了解一下就可以了: ~~~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; ~~~