Redis的Ziplist数据结构

本文详细介绍了Redis中压缩列表(ziplist)的数据结构及其内部存储机制。包括zlbytes、zltail、zllen等字段含义及作用,特别是entry的prevrawlen、len字段的变长编码方式,有助于理解Redis内存高效利用的原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

压缩列表是是hash和list的底层实现。他的数据结构如下:
这里写图片描述

  • zlbytes:32bit,ziplist占用的总字节数
  • zltail:32bit,标记最后一个entry所在的位置(偏移字节数),这样无需遍历整个ziplist就可以快速的定位到最后一项,方便进行push或pop操作
  • zllen:16bit,entry的总数量。当entry的实际数量超过zllen所能保存的最大值2^16-1时,有如下约定:entry数量小于等于2^16-2,zllen保存entry数量值;entry数量大于2^16-2,zllen=2^16-1,通过遍历ziplist获取实际的entry个数
  • entry:真正存放数据的数据项,有自己的数据结构,长度不定
  • zlend:ziplist结束标记,1字节,值为255

entry的结构:
prevrawlen | len | data

  • prevrawlen:上一个数据项长度,变长编码。用于反向遍历时快速查找上一数据项
  • len:当前数据项长度,变长编码
  • data:存储的数据

那么,上面提到的变长编码是什么呢?它是指redis里对该字段的自定义的可变长度的编码约定,不同的字段有不同的约定方式。比如:

prevrawlen的编码方式
两种可能,或者是1个字节,或者是5个字节:
1. 如果前一个数据项占用字节数小于254,那么就只用一个字节来表示,这个字节的值就是前一个数据项的>占用字节数。
2. 如果前一个数据项占用字节数大于等于254,那么就用5个字节来表示,其中第1个字节的值是254(作为这种情况的一个标记),而后面4个字节组成一个整型值,来真正存储前一个数据项的占用字节数。

len的编码方式
len字段就更加复杂了,它根据第1个字节的不同,总共分为9种情况(下面的表示法是按二进制表示):
1. |00pppppp| - 1 byte。第1个字节最高两个bit是00,那么字段只有1个字节,剩余的6个bit用来表示长度值,最高可以表示63 (2^6-1)。
2. |01pppppp|qqqqqqqq| - 2 bytes。第1个字节最高两个bit是01,那么字段占2个字节,总共有14个bit用来表示长度值,最高可以表示16383 (2^14-1)。
3. |10__|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes。第1个字节最高两个bit是10,那么len字段占5个字节,总共使用32个bit来表示长度值(6个bit舍弃不用),最高可以表示2^32-1。需要注意的是:在前三种情况下,都是按字符串来存储的;从下面第4种情况开始,开始变为按整数来存储了。
4. |11000000| - 1 byte。字段占用1个字节,值为0xC0,后面的数据存储为2个字节的int16_t类型。
5. |11010000| - 1 byte。字段占用1个字节,值为0xD0,后面的数据存储为4个字节的int32_t类型。
6. |11100000| - 1 byte。字段占用1个字节,值为0xE0,后面的数据存储为8个字节的int64_t类型。
7. |11110000| - 1 byte。字段占用1个字节,值为0xF0,后面的数据存储为3个字节长的整数。
8. |11111110| - 1 byte。字段占用1个字节,值为0xFE,后面的数据存储为1个字节的整数。
9. |1111xxxx| - - (xxxx的值在0001和1101之间)。这是一种特殊情况,xxxx从1到13一共13个值,这时就用这13个值来表示真正的数据。注意,这里是表示真正的数据,而不是数据长度了。也就是说,在这种情况下,后面不再需要一个单独的字段来表示真正的数据了,而是和合二为一了。另外,由于xxxx只能取0001和1101这13个值了(其它可能的值和其它情况冲突了,比如0000和1110分别同前面第7种第8种情况冲突,1111跟结束标记冲突),而小数值应该从0开始,因此这13个值分别表示0到12,即xxxx的值减去1才是它所要表示的那个整数数据的值。

以上便是ziplist在内存中的存储结构。至于为什么叫压缩列表,内存利用率是不是比一般的更高,暂时还没空研究。下次补充吧。

参考资料:
Redis之ziplist数据结构
Redis内部数据结构详解(4)——ziplist

### RedisZiplist 数据结构详解 ZiplistRedis 内部用于优化内存占用的一种紧凑型数据结构。它是一种压缩列表,主要用于存储小型的 List 和 Hash 数据类型[^1]。 #### Ziplist 的基本概念 Ziplist 实际上是一个双向链表的变体,但它通过将多个节点的数据连续存储在一块内存区域中来减少内存开销。相比传统的指针链接方式,ziplist 使用偏移量和长度字段代替指针,从而减少了因频繁分配小块内存带来的碎片化问题[^2]。 #### 底层实现细节 每条记录由三部分组成:前一节点的大小(previous entry length)、当前节点的内容长度(entry data length),以及实际存储的数据本身(data)。 - **Previous Entry Length**: 表示前面一个节点的总字节数,用来定位到下一个节点的位置。 - **Entry Data Length**: 当前节点所占的实际字节长度。 - **Data**: 存储的具体值,可以是整数或者字符串形式。 为了进一步节约空间,在某些情况下如果数值较小,则可以直接编码成特殊格式而无需额外开辟新位置保存完整的二进制表示[^3]。 #### 使用场景分析 由于其设计特点决定了适合处理少量项目且单个项目体积不大的情况: 1. 小规模列表或哈希映射; 2. 需要高效利用有限资源的应用环境比如嵌入式设备上的缓存服务端程序开发过程中可能会考虑采用这种方式来进行简单对象关系建模等等。 需要注意的是当元素过多或者过大时效率就会下降明显因为查找时间复杂度接近O(n),而且每次插入删除操作都可能导致整个序列重新布局调整造成较大延迟成本所以对于大规模动态变化频繁的数据集来说并不是最佳选择。 ```python # Python 示例展示如何模拟简单的 ziplist 结构 (仅作示意用途) class ZiplistNode: def __init__(self, prev_len, value): self.prev_len = prev_len # 前一项长度 self.value = value # 节点值 def create_ziplist(values): nodes = [] total_prev_length = 0 for val in values: node = ZiplistNode(total_prev_length, val) nodes.append(node) if isinstance(val, int): size = get_int_size(val) # 获取整数所需的空间大小 elif isinstance(val, str): size = len(val.encode('utf8')) # 字符串按 UTF-8 编码计算 total_prev_length += size # 更新累积前项长度 return nodes def get_int_size(num): """ 计算整数所需的最小字节数 """ import struct fmts = ['b', 'h', 'i', 'q'] sizes = [1, 2, 4, 8] for i,fmt in enumerate(fmts[:-1]): try: packed_num = struct.pack(f'>{fmt}', num) unpacked_num = struct.unpack(f'>{fmt}',packed_num)[0] if(unpacked_num == num): return sizes[i] except Exception as e: continue return sizes[-1] values = ["key", "value"] nodes = create_ziplist(values) for n in nodes: print(f'Prev Len:{n.prev_len}, Value:"{n.value}"') ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值