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 是非常常见的, 为此, 将 0127 范围内的数和 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

posted on 2022-01-17 22:06  长江同学  阅读(739)  评论(0编辑  收藏  举报