# 解密 ziplist.

为什么叫解密 ziplist 呢?因为从 ziplist 中取到我们预期的值,真的和解密一样!烧脑,但是极其有趣!!

# 引题

在介绍 Redis 的数据类型 list 的时候,是我们第一次接触 ziplist 这一数据结构。

不知道是否还记得 ziplist 这种数据结构的特性。如果不记得也没有关系,今天我们来详细的看下这个数据结构。

# 重读 ziplist 数据结构

ziplist 是经过特殊编码的双向链接列表,旨在提高内存效率。 它同时存储字符串和整数值,其中整数被编码为实际整数,而不是一系列字符。它允许在 O(1) 时间在列表的任一侧进行推和弹出操作。

但是,由于每个操作都需要重新分配 ziplist 使用的内存,因此实际的复杂性与 ziplist 使用的内存量有关。

ziplist 的数据结构总览。如下图

我来依次解释下这 5 个部分.

  • <uint32_t zlbytes> : 存储的 ziplist 占用的字节数 (包含 zlbytes 字段本身的 4 个字节)。因此:无需遍历就能直接知道这个数据结构的大小。

  • <uint32_t zltail> 是列表中最后一个条目的偏移量。 这允许在列表的另一端进行弹出操作,而无需完全遍历。在任意一端进行 poppush 操作的时间复杂度都是 O(1) .

  • <uint16_t zllen> 是条目数。当条目数超过 2 ^ 16-2 时,此值将设置为 2 ^ 16-1 ,这时,我们需要遍历整个列表以了解其包含多少项了。

  • <uint8_t zlend> 是代表 ziplist 末尾的特殊条目。编码为等于 255 的单个字节。

  • <entry> : 往下看.

# Entry

ziplist 中的每个条目都以包含两个信息的元数据作为前缀。首先,存储前一个 entry 的长度,以便能够从后到前遍历列表。第二,提供 entry 编码。 它表示条目类型,整数或字符串,对于字符串,还表示字符串有效负载的长度。因此,能够像下面这样存储一个完整的 entry .

有时, encoding 也可以代表 entry 本身,就像后面将要看到的小整数一样。在这种情况下, <entry-data> 部分丢失了,我们的 Entry 结构是这样的:

我们来具体看下 Entry 的编码方式.

# prevlen

<prevlen> 表示 前一个 entry 的长度。

  • 如果 前一个 entry 的长度小于 254 个字节,那么这个 Entry 只会消耗一个字节来表示该长度。一个无符号的 8 位整数。

  • 如果前一个 entry 长度大于或等于 254 字节的时候,它将占用 5 个字节。第一个字节设置为 254 ( FE ), 以指示随后的值更大。 其他的四个字节将作为前一个 entry 的长度的值。

所以,我们就能知道了 下面这种编码方式.

<prevlen form 9 to 253> <encoding> <entry-data>

或者

<prevlen 4 bytes unsigned little endian prevlen> <encoding> <entry-data>

# encoding

entry 中的 encoding 字段取决于 entry-data 的内容。

  • 如果 entry-data 是字符串的时候, encoding 第一个字节的前两位用于存储字符串长度的编码类型。然后是字符串的实际长度。

  • 如果 entry 是整数时,前 2 位都是 1 ,。 接下来的两位用于指定在此标头之后将存储哪种类型.

由上面的两点,就足以确定 entry-data 的类型.

不同的类型和编码的概述如下:

# 字符串类型
  • a . | 00pppppp | : 含义是占用 1 个字节来表示长度小于或等于 63 个字节( 6 位)的字符串值。  pppppp 表示无符号的 6 位长度。

  • b . |01pppppp|qqqqqqqq| 含义是用 2 个字节来表示小于等于 16383(2^14) 个字节的字符串的长度. (这里使用大端法计数)

  • c . |10000000|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| 含义是 使用 5 个字节来表示大于等于 16384 个字节长度的字符串,只有 4 个字节表示其最大长度为 2^32-1 . 低位的 6 位被设置成 0 .(这里使用大端法计数)

# 整数类型
  • d . |11000000| : 含义是用 3 个字节表示一个 16 位的有符号的短整形数 ( short )。
  • e . |11010000| : 含义是用 5 个字节 表示一个 32 位的有符合整数型 ( int ).
  • f . |11100000| : 含义是用 9 个字节,表示一个 64 位的有符合长整数 ( long )
  • g . |11110000| : 含义是用 4 个字节表示 24 位有符号整数 (只占用 3 个字节)。PS: 有什么作用呢?前面说的小整形数。节约空间。即如图
  • h . |11111110| : 含义是用 2 个字节表示一个 8 位的有符号小整形数。 1 个字节。
  • i . |1111xxxx| :
    除去上面列举的几类编码标识,还有 |1111xxxx| 的类别。
    ( xxxx00001101 之间) 立即 4 位整数。 012 之间的无符号整数。编码值实际上是 113 ,因为不能使用 00001111 ,因此应从编码的 4 位值中减去 1 以获得正确的值。
  • j . |11111111| - 表示 ziplist 的特殊 entry

这样,整个 Entry 的编码方式我们就搞明白了。
这些都是 Redis 中定义的规定, 所以我们记住就行了。如果让我们自己去设计一套编码方案的时候,我们就可以参考这种思路去设计。

# 那我们来举两个例子来试试身手吧。

# 例子 1: 25

# 编码方式

[0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff]

# 解析过程

ziplist-01-Ziplist例子的结构1.png

# 例子 2: Hello World

这里我们还是接着上面例子来讲,我们再设置一个 字符串 Hello World

# 编码格式

[02] [0b] [48 65 6c 6c 6f 20 57 6f 72 6c 64]

# 解析过程

ziplist-01-Ziplist例子的结构2.png

# 附录,ASCII 码表 (可展示字符)

以上内容,就是 Redisziplist 结构的内容了。

# 总结

本篇文章的内容主要是更加详细的分享了 ziplist 的这种数据结构的内部结构以及编码方式.

  • ziplist5 部分组成。存储了相关信息: <整个ziplist的长度><最后一个entry的偏移量><entry的总数><entries><表示结束的特殊entry>
  • 一个 entry3 部分组成, <前一个entry的长度><编码方式><entry的内容>
  • 其中 编码方式 是由 entry 的内容 决定的。有 10 条标准 (规定 / 协议)
  • 引用了两个示例 ( 25Hello World ),根据 Redis 的方式来解码.

# 最后

期待您的关注,希望和你一起遇见更好的自己

二维码