redis学习-17 redis内部实现之ziplist

1、概念

ziplist是一个经过特殊编码的双向链表,它的设计目标就是为了提高存储效率。

ziplist可以用于存储字符串或整数,其中整数是按真正的二进制表示进行编码的,

而不是编码成字符串序列。它能以O(1)的时间复杂度在表的两端提供push和pop操作。

2、提高存储效率

一个普通的双向链表,链表中每一项都占用独立的一块内存,各项之间用地址指针(或引用)连接起来。

这种方式会带来大量的内存碎片,而且地址指针也会占用额外的内存。

(1)而ziplist却是将表中每一项存放在前后连续的地址空间内,一个ziplist整体占用一大块内存。它是一个表(list),但其实不是一个链表(linked list)。

(2)ziplist为了在细节上节省内存,对于值的存储采用了变长的编码方式,大概意思是说,

对于大的整数,就多用一些字节来存储,而对于小的整数,就少用一些字节来存储。

3、ziplist的内存结构

<zlbytes><zltail><zllen><entry>...<entry><zlend>

各个部分在内存上是前后相邻的,它们分别的含义如下:

<zlbytes>: 32bit,表示ziplist占用的字节总数(也包括<zlbytes>本身占用的4个字节)。

<zltail>: 32bit,表示ziplist表中最后一项(entry)在ziplist中的偏移字节数。<zltail>的存在,使得我们可以很方便地找到最后一项(不用遍历整个ziplist),从而可以在ziplist尾端快速地执行push或pop操作。

<zllen>: 16bit, 表示ziplist中数据项(entry)的个数。zllen字段因为只有16bit,所以可以表达的最大值为2^16-1。

注:如果ziplist中数据项个数超过了16bit能表达的最大值,ziplist仍然可以来表示。

如果<zllen>小于等于2^16-2(也就是不等于2^16-1),那么<zllen>就表示ziplist中数据项的个数;

否则,那么<zllen>就不表示数据项个数了,这时候要想知道ziplist中数据项总数,那么必须对ziplist从头到尾遍历各个数据项,才能计数出来。

<entry>: 表示真正存放数据的数据项,长度不定。一个数据项(entry)也有它自己的内部结构,这个稍后再解释。

<zlend>: ziplist最后1个字节,是一个结束标记,值固定等于255。

注:ziplist采取的是小端模式来存储

4、数据项<entry>的构成

<prevrawlen><len><data>

我们看到在真正的数据(<data>)前面,还有两个字段:

<prevrawlen>: 表示前一个数据项占用的总字节数。这个字段的用处是为了让ziplist能够从后向前遍历(从后一项的位置,只需向前偏移prevrawlen个字节,就找到了前一项)。这个字段采用变长编码。

<len>: 表示当前数据项的数据长度(即<data>部分的长度)。也采用变长编码。

那么<prevrawlen><len>是怎么进行变长编码的呢?各位读者打起精神了,我们终于讲到了ziplist的定义中最繁琐的地方了。

(1)先说<prevrawlen>。它有两种可能,或者是1个字节,或者是5个字节:

如果前一个数据项占用字节数小于254,那么<prevrawlen>就只用一个字节来表示,这个字节的值就是前一个数据项的占用字节数。
如果前一个数据项占用字节数大于等于254,那么<prevrawlen>就用5个字节来表示,其中第1个字节的值是254(作为这种情况的一个标记),而后面4个字节组成一个整型值,来真正存储前一个数据项的占用字节数。
有人会问了,为什么没有255的情况呢?

这是因为:255已经定义为ziplist结束标记<zlend>的值了。在ziplist的很多操作的实现中,都会根据数据项的第1个字节是不是255来判断当前是不是到达ziplist的结尾了,

因此一个正常的数据的第1个字节(也就是<prevrawlen>的第1个字节)是不能够取255这个值的,否则就冲突了。

(2)而<len>字段就更加复杂了,它根据第1个字节的不同,总共分为9种情况(下面的表示法是按二进制表示):

|00pppppp| - 1 byte。第1个字节最高两个bit是00,那么<len>字段只有1个字节,剩余的6个bit用来表示长度值,最高可以表示63 (2^6-1)。

|01pppppp|qqqqqqqq| - 2 bytes。第1个字节最高两个bit是01,那么字段占2个字节,总共有14个bit用来表示长度值,最高可以表示16383 (2^14-1)。

|10__|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes。第1个字节最高两个bit是10,那么len字段占5个字节,总共使用32个bit来表示长度值(6个bit舍弃不用),
最高可以表示2^32-1。需要注意的是:在前三种情况下,<data>都是按字符串来存储的;从下面第4种情况开始,开始变为按整数来存储了。

|11000000| - 1 byte。字段占用1个字节,值为0xC0,后面的数据<data>存储为2个字节的int16_t类型。

|11010000| - 1 byte。字段占用1个字节,值为0xD0,后面的数据<data>存储为4个字节的int32_t类型。

|11100000| - 1 byte。字段占用1个字节,值为0xE0,后面的数据<data>存储为8个字节的int64_t类型。

|11110000| - 1 byte。字段占用1个字节,值为0xF0,后面的数据<data>存储为3个字节长的整数。

|11111110| - 1 byte。字段占用1个字节,值为0xFE,后面的数据<data>存储为1个字节的整数。

|1111xxxx| - - (xxxx的值在0001和1101之间)。这是一种特殊情况,xxxx从1到13一共13个值,这时就用这13个值来表示真正的数据。

注意,这里是表示真正的数据,而不是数据长度了。也就是说,在这种情况下,后面不再需要一个单独的<data>字段来表示真正的数据了,
而是<len><data>合二为一了。另外,由于xxxx只能取0001和1101这13个值了(其它可能的值和其它情况冲突了,比如0000和1110分别同前面第7种第8种情况冲突,1111跟结束标记冲突),
而小数值应该从0开始,因此这13个值分别表示0到12,即xxxx的值减去1才是它所要表示的那个整数数据的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值