listpack是ziplist结构的改进版,在存储空间上更加节省,而且比ziplist也更精简。
struct listpack<T>{ int32 total_bytes; //总字节数 int16 size; //元素个数 T[] entries; //紧促排列的元素列表 int8 end; //与zlend一样,恒为0xFF }
内部结构示意图:
listpack和ziplist结构很相似,只是少了一个zltail_offset字段,这个字段是zl用来定位最后一个元素的位置,用于逆序遍历的,不过listpack可以用其它方式来定位出最后一个元素的位置,所以省略了这个字段。
struct lpentry{ int<var> encoding; optional byte[] content; int<var> length; }
之前说过lp省略了zltail_offset这个字段,但也可以知道尾部元素的位置,而且不用存储前一个元素的长度,从而避免了级联更新,最根本的原因就在于它把元素的长度信息放在了尾部,而且通过一定的规则使得程序可以一个字节一个字节往前读取,根据读取到的信息就能知道元素有多长,从而判断出元素和上一个元素的界限。
虽然lp比zl的好处多,但由于zl已经被广泛使用,所以被取代还需要时间。目前使用了lp结构的也就是新增加stream数据结构中。
lp的元素长度的编码可以是1-5个字节中的任意长度,它通过字节的最高位是否为1来决定编码的长度。以下是具体的区分:
小的 integer 对于一些比较小的数, 比如 1, 2, 66 是非常常见的, 为此, 将 0 到 127 范围内的数和 encoding-type 一起编码, 省去了 entry-data 部分, 也就是0|xxxxxxx形式, 例如: "x03" --> 0|0000011 --> 3 "x12" --> 0|0010010 --> 18 短的 string 同样的, 短的字符串也很常见, 所以为长度不超过 63 的字符串如下编码: 10|xxxxxx <entry-data> 其中, xxxxxx是 6 位无符号整数, 表示字符串的长度, 例子: "x40" --> 10|000000 --> 空字符串 "x45hello" --> 10|001001 hello --> 字符串"hello" 更大的编码方式 当 encoding-type 的最高 2 位都是 1 时, 此时就是另一种编码方式: 110|xxxxx yyyyyyyy --> 13位有符号整数 1110|xxxx yyyyyyyy <entry-data> --> 长度不超过4095的字符串 当以上都不能满足时, 才用到下面这些编码方式: 1111|0000 <4 bytes len> <entry-data> --> 非常大的字符串 1111|0001 <entry-data> --> 16位有符号整数 1111|0010 <entry-data> --> 24位有符号整数 1111|0011 <entry-data> --> 32位有符号整数 1111|0100 <entry-data> --> 64位有符号整数 1111|0101 - 1111|1110 --> 未使用 1111|1111-0xFF为结束符
【参考】
《Redis深度历险 核心原理与应用实践》
https://www.dazhuanlan.com/zws0932/topics/1132686