Redis 设计与实现 5:压缩列表 ziplist

压缩列表是 ZSET、HASH和 LIST 类型的其中一种编码的底层实现,是由一系列特殊编码的连续内存块组成的顺序型数据结构,其目的是节省内存。

ziplist 的结构

外层结构

下图展示了压缩列表的组成:
ziplist 的结构

各个字段的含义如下:

  1. zlbytes:是一个无符号 4 字节整数,保存着 ziplist 使用的内存数量。
    通过 zlbytes,程序可以直接对 ziplist 的内存大小进行调整,无须为了计算 ziplist 的内存大小而遍历整个列表。
  2. zltail:压缩列表 最后一个 entry 距离起始地址的偏移量,占 4 个字节。
    这个偏移量使得对表尾的 pop 操作可以在无须遍历整个列表的情况下进行。
  3. zllen:压缩列表的节点 entry 数目,占 2 个字节。
    当压缩列表的元素数目超过 2^16 - 2 的时候,zllen 会设置为2^16-1,当程序查询到值为2^16-1,就需要遍历整个压缩列表才能获取到元素数目。所以 zllen 并不能替代 zltail
  4. entryX:压缩列表存储数据的节点,可以为字节数组或者整数。
  5. zlend:压缩列表的结尾,占一个字节,恒为 0xFF

实现的代码 ziplist.c 中,ziplist 定义成了宏属性。

// 相当于 zlbytes,ziplist 使用的内存字节数
#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))
// 相当于 zltail,最后一个 entry 距离 ziplist 起始位置的偏移量
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
// 相当于 zllen,entry 的数量
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
// zlbytes + zltail + zllen 的长度,也就是 4 + 4 + 2 = 10
#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))
// zlend 的长度,1 字节
#define ZIPLIST_END_SIZE        (sizeof(uint8_t))
// 指向第一个 entry 起始位置的指针
#define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)
// 指向最后一个 entry 起始位置的指针
#define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
// 相当于 zlend,指向 ziplist 最后一个字节
#define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)

以下是重建新的空 ziplist 的代码实现,在 ziplist.c 中:

unsigned char *ziplistNew(void) {
	// ziplist 头加上结尾标志字节数,就是 ziplist 使用内存的字节数了
    unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE;
    unsigned char *zl = zmalloc(bytes);
    ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);
    // 因为没有 entry 列表,所以尾部偏移量是 ZIPLIST_HEADER_SIZE
    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);
    // entry 节点数量是 0
    ZIPLIST_LENGTH(zl) = 0;
    // 设置尾标识。
    // #define ZIP_END 255 
    zl[bytes-1] = ZIP_END;
    return zl;
}

entry 节点的结构

布局

节点的结构一般是:<prevlen> <encoding> <entry-data>

  • prevlen:前一个 entry 的大小,用于反向遍历。
  • encoding:编码,由于 ziplist 就是用来节省空间的,所以 ziplist 有多种编码,用来表示不同长度的字符串或整数。
  • data:用于存储 entry 真实的数据;

prevlen

节点的 prevlen 属性以字节为单位,记录了压缩列表中前一个节点的长度。编码长度可以是 1 字节或者 5 字节。

  • 当前面节点长度小于 254 的时候,长度为 1 个字节。
  • 当前面节点长度大于 254 的时候,1 个字节不够存了。前面第一个字节就设置为 254,后面 4 个字节才是真正的前面节点的长度。

下图展示了 1 字节 和 5 字节 prevlen 的示意图(来源)
不同长度的 prevlen 示意图

prevlen 属性主要的作用是反向遍历。通过 ziplist 的 zltail,我们可以得到最后一个节点的位置,接着可以获取到前一个节点的长度 len,指针向前移动 len,就是指向倒数第二个节点的位置了。以此类推,可以一直往前遍历。

encoding

encoding 记录了节点的 data 属性所保存数据的类型和长度。类型主要有两种:字符串和整数。

类型 1. 字符串

如果 encoding 以 0001 或者 10 开头,就表示数据类型是字符串

#define ZIP_STR_06B (0 << 6)
#define ZIP_STR_14B (1 << 6)
#define ZIP_STR_32B (2 << 6)

字符串有三种编码:

  • 长度 < 2^6 时,以 00 开头,后 6 位表示 data 的长度,。
  • 2^6 <= 长度 < 2^14 时,以 01 开头,后续 6 位 + 下一个字节的 8 位 = 14 位表示 data 的长度。
  • 2^14 <= 长度 < 2^32 字节时,以 10 开头,后续 6 位不用,从下一字节起连续 32 位表示 data 的长度。

下图为字符串三种长度结构的示意图(来源):
ziplist 字符串编码示意图

类型 2. 整数

如果 encoding 以 11 开头,就表示数据类型是整数

#define ZIP_INT_16B (0xc0 | 0<<4)
#define ZIP_INT_32B (0xc0 | 1<<4)
#define ZIP_INT_64B (0xc0 | 2<<4)
#define ZIP_INT_24B (0xc0 | 3<<4)
#define ZIP_INT_8B 0xfe

#define ZIP_INT_IMM_MIN 0xf1    /* 11110001 */
#define ZIP_INT_IMM_MAX 0xfd    /* 11111101 */

整数一共有 6 种编码,说起来麻烦,看图吧(来源)。
ziplist 整数编码示意图
看了上图的最后一个类型,可能有小伙伴就有疑问:为啥没有 11111111 ?
答:因为 11111111 表示 zlend (十进制的 255,十六进制的 oxff)

data

data 表示真实存的数据,可以是字符串或者整数,从编码可以得知类型和长度。知道长度,就知道 data 的起始位置了。

比较特殊的是,整数 1 ~ 13 (0001 ~ 1101),因为比较短,刚好可以塞在 encoding 字段里面,所以就没有 data

连锁更新

通过上面的分析,我们知道:

  • 前个节点的长度小于 254 的时候,用 1 个字节保存 prevlen
  • 前个字节的长度大于等于 254 的时候,用 5 个字节保存 prevlen

现在我们来考虑一种情况:假设一个压缩列表中,有多个长度 250 ~ 253 的节点,假设是 entry1 ~ entryN。
因为都是小于 254,所以都是用 1 个字节保存 prevlen。https://fontsup.com/profile/39glmzfoxjr88-67.html
https://fontsup.com/profile/liked/39glmzfoxjr88-67.html
https://fontsup.com/profile/sent/39glmzfoxjr88-67.html
https://fontsup.com/profile/88izrv90kub67-98.html
https://fontsup.com/profile/liked/88izrv90kub67-98.html
https://fontsup.com/profile/sent/88izrv90kub67-98.html
https://fontsup.com/profile/rvh2b213khr95-2.html
https://fontsup.com/profile/liked/rvh2b213khr95-2.html
https://fontsup.com/profile/sent/rvh2b213khr95-2.html
https://fontsup.com/profile/d00m0jj9qyd35-27.html
https://fontsup.com/profile/liked/d00m0jj9qyd35-27.html
https://fontsup.com/profile/sent/d00m0jj9qyd35-27.html
https://fontsup.com/profile/8oc0vm1tpja07-5.html
https://fontsup.com/profile/liked/8oc0vm1tpja07-5.html
https://fontsup.com/profile/sent/8oc0vm1tpja07-5.html
https://fontsup.com/profile/yssv9567msu03-17.html
https://fontsup.com/profile/liked/yssv9567msu03-17.html
https://fontsup.com/profile/sent/yssv9567msu03-17.html
https://fontsup.com/profile/42wyv0g1aob51-85.html
https://fontsup.com/profile/liked/42wyv0g1aob51-85.html
https://fontsup.com/profile/sent/42wyv0g1aob51-85.html
https://fontsup.com/profile/zwx8u626lhy33-83.html
https://fontsup.com/profile/liked/zwx8u626lhy33-83.html
https://fontsup.com/profile/sent/zwx8u626lhy33-83.html
https://fontsup.com/profile/dxeal41wjkx36-39.html
https://fontsup.com/profile/liked/dxeal41wjkx36-39.html
https://fontsup.com/profile/sent/dxeal41wjkx36-39.html
https://fontsup.com/profile/u0f5wnswcqq10-55.html
https://fontsup.com/profile/liked/u0f5wnswcqq10-55.html
https://fontsup.com/profile/sent/u0f5wnswcqq10-55.html
https://fontsup.com/profile/9ltxp6y9pew76-48.html
https://fontsup.com/profile/liked/9ltxp6y9pew76-48.html
https://fontsup.com/profile/sent/9ltxp6y9pew76-48.html
https://fontsup.com/profile/o9pk4c25ibg78-41.html
https://fontsup.com/profile/liked/o9pk4c25ibg78-41.html
https://fontsup.com/profile/sent/o9pk4c25ibg78-41.html
https://fontsup.com/profile/v8cuoffbrzg75-52.html
https://fontsup.com/profile/liked/v8cuoffbrzg75-52.html
https://fontsup.com/profile/sent/v8cuoffbrzg75-52.html
https://fontsup.com/profile/2etha1wdknv43-17.html
https://fontsup.com/profile/liked/2etha1wdknv43-17.html
https://fontsup.com/profile/sent/2etha1wdknv43-17.html
https://fontsup.com/profile/qhxyjpuwuyh07-38.html
https://fontsup.com/profile/liked/qhxyjpuwuyh07-38.html
https://fontsup.com/profile/sent/qhxyjpuwuyh07-38.html
https://fontsup.com/profile/alnc2n72eif24-5.html
https://fontsup.com/profile/liked/alnc2n72eif24-5.html
https://fontsup.com/profile/sent/alnc2n72eif24-5.html
https://fontsup.com/profile/6gjjv0uqwke41-59.html
https://fontsup.com/profile/liked/6gjjv0uqwke41-59.html
https://fontsup.com/profile/sent/6gjjv0uqwke41-59.html
https://fontsup.com/profile/ur5xo6xaffp27-23.html
https://fontsup.com/profile/liked/ur5xo6xaffp27-23.html
https://fontsup.com/profile/sent/ur5xo6xaffp27-23.html
https://fontsup.com/profile/xgvcbxemyqp85-80.html
https://fontsup.com/profile/liked/xgvcbxemyqp85-80.html
https://fontsup.com/profile/sent/xgvcbxemyqp85-80.html
https://fontsup.com/profile/q30z7soglnj67-97.html
https://fontsup.com/profile/liked/q30z7soglnj67-97.html
https://fontsup.com/profile/sent/q30z7soglnj67-97.html
https://fontsup.com/profile/v09n54s6bsd09-28.html
https://fontsup.com/profile/liked/v09n54s6bsd09-28.html
https://fontsup.com/profile/sent/v09n54s6bsd09-28.html
https://fontsup.com/profile/h05ovbh6unm06-86.html
https://fontsup.com/profile/liked/h05ovbh6unm06-86.html
https://fontsup.com/profile/sent/h05ovbh6unm06-86.html
https://fontsup.com/profile/srzjfeo3acw87-18.html
https://fontsup.com/profile/liked/srzjfeo3acw87-18.html
https://fontsup.com/profile/sent/srzjfeo3acw87-18.html
https://fontsup.com/profile/84kfc636deo86-39.html
https://fontsup.com/profile/liked/84kfc636deo86-39.html
https://fontsup.com/profile/sent/84kfc636deo86-39.html
https://fontsup.com/profile/02464ro7dwc88-30.html
https://fontsup.com/profile/liked/02464ro7dwc88-30.html
https://fontsup.com/profile/sent/02464ro7dwc88-30.html
https://fontsup.com/profile/bss4dzxujrk35-53.html
https://fontsup.com/profile/liked/bss4dzxujrk35-53.html
https://fontsup.com/profile/sent/bss4dzxujrk35-53.html
https://fontsup.com/profile/k14uq3vxbcv12-11.html
https://fontsup.com/profile/liked/k14uq3vxbcv12-11.html
https://fontsup.com/profile/sent/k14uq3vxbcv12-11.html
https://fontsup.com/profile/pltvq8iibat45-46.html
https://fontsup.com/profile/liked/pltvq8iibat45-46.html
https://fontsup.com/profile/sent/pltvq8iibat45-46.html
https://fontsup.com/profile/pg8nzj47irn28-62.html
https://fontsup.com/profile/liked/pg8nzj4

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值