redis6.0 源码学习(五)ziplist

redis6.0源码学习(五)ziplist文章目录redis6.0源码学习(五)ziplist一、数据结构二、代码解析1、创建2、查找3、插入三、总结一、数据结构ziplist是经过特殊编码的双向链接列表,该列表具有很高的内存效率。它存储字符串和整数值,其中整数被编码为实际整数,而不是一系列个字符。它允许对列表的两侧进行push和pop操作且复杂度为O(1)。但是由于每个操作都需要重新分配ziplist使用的内存,实际复杂度与ziplist使用的内存量有关。下图是ziplist得示意图:

大家好,又见面了,我是你们的朋友全栈君。

redis6.0 源码学习(五)ziplist

一、数据结构

ziplist是经过特殊编码的双向链接列表,该列表具有很高的内存效率。 它存储字符串和整数值,其中整数被编码为实际整数,而不是一系列个字符。 它允许对列表的两侧进行push和pop操作且复杂度为O(1)。 但是由于每个操作都需要重新分配ziplist使用的内存,实际复杂度与ziplist使用的内存量有关。

下图是ziplist得示意图:

在这里插入图片描述)

各个字段详细解释如下:

属性 类型 长度 用途
zlbytes uint32_t 4字节 记录整个压缩列表占用的内存字节数,在对压缩列表进行内存重分配或者计算zlend的位置时使用。
zltail uint32_t 4字节 记录压缩列表尾节点距离压缩列表起始地址有多少个字节,通过这个偏移量,程序可以直接获得压缩列表的表尾节点地址
zllen uint16_t 2字节 记录了压缩列表的节点数量。当这个属性的值小于65535时(即:小于UINT16_MAX),这个属性的值就是压缩列表的节点数量,但是当这个值等于UINT16_MAX时,需要遍历整个压缩列表才能获得其节点数量
entryX 列表节点 不定 压缩表的各个节点,X代表数量不定
zlend uint8_t 1字节 特殊常数值(OxFF,也就是255)。用于标记压缩列表的末端。

ziplist中的每个entry均包含两个信息:prevlen 和 encoding。prevlen 字段:存储前一个条目的长度,以便能够从后到前遍历列表。encoding表示entry类型,整数或字符串,对于字符串,还表示字符串有效负载的长度。 因此,完整的entry存储形式如下:

在这里插入图片描述)

在存储较小的整数的时候,entry-data字段会不存在,因为也存储在encoding字段中了。

1、prevlen:针对prevlen的大小不同,prevlen占用的字段长度也不一样。

  • prevlen < 254

    prevlen占用1个字节 8位

  • prevlen > 254

    此时prevlen占用5个字节,第一个字节为 0xFE,后续四个字节为真正的长度。 0xFE表示是下一个entry一个大的值。

2、encoding 根据entry-data存入的是字符串还是整数具有不同的格式。具体如下:

  • a:字符串

在这里插入图片描述

当字符串小于63字节时(2^6),节点存为上图的第一种类型,高2位为00,低6位表示data的长度。

当字符串小于16383字节时(2^14),节点存为上图的第二种类型,高2位为01,后续14位表示data的长度。

当字符串小于4294967296字节时(2^32),节点存为上图的第三种类型,高2位为10,下一字节起连续32位表示data的长度。

  • b:整数:整数节点的encoding的长度为8位,其中高2位用来区分整数节点和字符串节点(高2位为11时是整数节点),低6位用来区分整数节点的类型,具体如下:

在这里插入图片描述

二、代码解析

主要有以下接口,这里只分析几个,其他的大家可以自行去看源代码。

unsigned char *ziplistNew(void);
unsigned char *ziplistMerge(unsigned char **first, unsigned char **second);
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where);
unsigned char *ziplistIndex(unsigned char *zl, int index);
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p);
unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p);
unsigned int ziplistGet(unsigned char *p, unsigned char **sval, unsigned int *slen, long long *lval);
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen);
unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p);
unsigned char *ziplistDeleteRange(unsigned char *zl, int index, unsigned int num);
unsigned int ziplistCompare(unsigned char *p, unsigned char *s, unsigned int slen);
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip);
unsigned int ziplistLen(unsigned char *zl);
size_t ziplistBlobLen(unsigned char *zl);

1、创建

可以对照图1 ziplist结构示意图 看这个创建的过程。

/* Create a new empty ziplist. */
unsigned char *ziplistNew(void) { 

unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE;  //<zlbytes>4字节<zltail>4字节<zllen>2字节<zlend>1字节,没有entry节点
unsigned char *zl = zmalloc(bytes); //申请内存
ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);//zlbytes赋值
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);//zltail赋值
ZIPLIST_LENGTH(zl) = 0; //zllen赋值
zl[bytes-1] = ZIP_END; //zlend赋值
return zl;
}

2、查找

/* Find pointer to the entry equal to the specified entry. Skip 'skip' entries * between every comparison. Returns NULL when the field could not be found. */
//返回p节点之后data与vstr(长度是vlen)相等的节点,只找p节点之后每隔skip的节点
//时间复杂度 O(n)
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip) { 

int skipcnt = 0;
unsigned char vencoding = 0;
long long vll = 0;
while (p[0] != ZIP_END) { 
  /* ZIP_END: Special "end of ziplist" entry. */
unsigned int prevlensize, encoding, lensize, len;
unsigned char *q;
ZIP_DECODE_PREVLENSIZE(p, prevlensize); //获取prevlen的字节数1还是5
/* 获取'ptr'中 entry encoding type 和 data length (字符串长度或者整数使用的bytes ) 。 * encoding变量: entry的encoding值 *lensize变量:encode entry所需要的bytes数 *len变量: hold the entry length. */        
ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);
q = p + prevlensize + lensize; //当前节点的data
if (skipcnt == 0) { 

/* Compare current entry with specified entry */
if (ZIP_IS_STR(encoding)) { 
//判断当前节点是不是字符串节点
if (len == vlen && memcmp(q, vstr, vlen) == 0) { 
 //字符串比较
return p;
}
} else { 

/* Find out if the searched field can be encoded. Note that * we do it only the first time, once done vencoding is set * to non-zero and vll is set to the integer value. */
if (vencoding == 0) { 
 //这个代码块只会执行一次,判断vstr能够用整数表示 
/* 校验'vstr'指向的内容能否转换成整数,'vll'存这个整数值 'encoding'存编码格式 */
if (!zipTryEncoding(vstr, vlen, &vll, &vencoding)) { 

/* If the entry can't be encoded we set it to * UCHAR_MAX so that we don't retry again the next * time. */
vencoding = UCHAR_MAX;
}
/* Must be non-zero by now */
assert(vencoding); 
}
/* Compare current entry with specified entry, do it only * if vencoding != UCHAR_MAX because if there is no encoding * possible for the field it can't be a valid integer. */
if (vencoding != UCHAR_MAX) { 
 //entry地址的内容转换成整数
long long ll = zipLoadInteger(q, encoding);
if (ll == vll) { 

return p; //相等的话返回
}
}
}
/* Reset skip count */
skipcnt = skip;
} else { 

/* Skip entry */
skipcnt--;
}
/* Move to next entry */
p = q + len;
}
return NULL;
}

3、插入

/* Insert an entry at "p". */
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) { 

return __ziplistInsert(zl,p,s,slen);
}
/* Insert item at "p". */
unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) { 

size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen; // 当前长度和插入节点后需要的长度
unsigned int prevlensize, prevlen = 0; // 前置节点长度和编码该长度值所需的长度
size_t offset;
int nextdiff = 0;
unsigned char encoding = 0;
long long value = 123456789; /* initialized to avoid warning. Using a value that is easy to see if for some reason we use it uninitialized. */
zlentry tail;
/* Find out prevlen for the entry that is inserted. */
// 找出待插入节点的前置节点长度
// 如果p[0]不指向列表末端,说明列表非空,并且p指向其中一个节点
if (p[0] != ZIP_END) { 

// 获取前置节点p的长度和编码该长度需要的字节
ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
} else { 
        
unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
if (ptail[0] != ZIP_END) { 
// 如果p指向列表末端,表示列表为空
prevlen = zipRawEntryLength(ptail);
}
}
/* See if the entry can be encoded */
if (zipTryEncoding(s,slen,&value,&encoding)) { 
//尝试encoding成整数
/* 'encoding' is set to the appropriate integer encoding */
reqlen = zipIntSize(encoding); //获取编码长度
} else { 

/* 'encoding' is untouched, however zipStoreEntryEncoding will use the * string length to figure out how to encode it. */
reqlen = slen;
}
/* We need space for both the length of the previous entry and * the length of the payload. */
reqlen += zipStorePrevEntryLength(NULL,prevlen); // 获取前置节点的编码长度
reqlen += zipStoreEntryEncoding(NULL,encoding,slen); // 获取当前节点的编码长度
/* When the insert position is not equal to the tail, we need to * make sure that the next entry can hold this entry's length in * its prevlen field. */
// 只要不是列表的末端插入,需要计算出下一个元素保存本元素prevlen字段空间是否足够, 不够时计算出欠缺的差值 
// nextdiff大于0,那就说明需要对当前p指向的节点的header进行扩展
int forcelarge = 0;
nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
if (nextdiff == -4 && reqlen < 4) { 

nextdiff = 0;
forcelarge = 1;
}
/* Store offset because a realloc may change the address of zl. */
offset = p-zl;// 存储p相对于列表zl的偏移地址
zl = ziplistResize(zl,curlen+reqlen+nextdiff); // 重新分配空间,curlen当前列表的长度
p = zl+offset; // 重新获取p的值
/* Apply memory move when necessary and update tail offset. */
if (p[0] != ZIP_END) { 
 // 非表尾插入,需要重新计算表尾的偏移量
/* Subtract one because of the ZIP_END bytes */
memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);/* 移动p原有位置和后面的内容到新的位置 */      
/* Encode this entry's raw length in the next entry. */   
if (forcelarge)
zipStorePrevEntryLengthLarge(p+reqlen,reqlen);
else
zipStorePrevEntryLength(p+reqlen,reqlen);
/* Update offset for tail */
// 更新列表尾相对于表头的偏移量,将新节点的长度算上
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);
/* When the tail contains more than one entry, we need to take * "nextdiff" in account as well. Otherwise, a change in the * size of prevlen doesn't have an effect on the *tail* offset. */
// 如果新节点后面有多个节点,那么表尾的偏移量需要算上nextdiff的值
zipEntry(p+reqlen, &tail);
if (p[reqlen+tail.headersize+tail.len] != ZIP_END) { 

ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
}
} else { 

// 表尾插入,直接计算偏移量
/* This element will be the new tail. */
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);
}
/* When nextdiff != 0, the raw length of the next entry has changed, so * we need to cascade the update throughout the ziplist */
// 当nextdiff不为0时,表示需要新节点的后继节点对头部进行扩展
//说明下一个元素需要扩展空间存放prevlen字段, 由于下一个元素空间变大, 有可能引起下下一个元素空间需要扩展, 下面函数检测后面元素, 并在需要时重置元素prevlen长度
if (nextdiff != 0) { 

// 需要对p所指向的header进行扩展更新
// 有可能会引起连锁更新
offset = p-zl;
zl = __ziplistCascadeUpdate(zl,p+reqlen);
p = zl+offset;
}
/* Write the entry */
p += zipStorePrevEntryLength(p,prevlen); // 将新节点前置节点的长度写入新节点的header
p += zipStoreEntryEncoding(p,encoding,slen); // 将新节点的值长度写入新节点的header
if (ZIP_IS_STR(encoding)) { 
 // 写入节点值
memcpy(p,s,slen);
} else { 

zipSaveInteger(p,value,encoding);
}
ZIPLIST_INCR_LENGTH(zl,1);  // 更新列表节点计数
return zl;
}

三、总结

  • ziplist是为节省内存空间而生的。
  • ziplist是一个为Redis专门提供的底层数据结构之一,本身可以有序也可以无序。当作hash的底层实现时,节点之间没有顺序;当作为zset的底层实现时,节点之间会按照大小顺序排列。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/128953.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 英语单词词性_英语单词词性总结

    英语单词词性_英语单词词性总结英语单词词性n.名词v.动词pron.代词adj.形容词adv.副词num.数词art.冠词prep.介词conj.连词interj.感叹词英语词性缩写pre

  • codeforces 437C The Child and Toy

    codeforces 437C The Child and Toy

  • ZigBee集成开发环境IAR安装

    一、Zigbee概述1.什么是ZigbeeZigBee是一种近距离、低复杂度的双向无线通信系统,主要用于距离短、功耗低、传输速率不高的电子设备之间进行数据传输,且具有低功耗、低成本、大容量、时延短、可靠性高以及网络拓扑结构灵活的特点。Zigbee本质就是无线设备之间的一种通信方式,类似于人和人之间用普通话交流,普通话就是一种通信方式。Zigbee,Zigbee通信方式,Zigbee协议说的都是一回事。Zigbee的主要作用是用来构建无线局域网。2.各通信方式的比较蓝牙:功耗比较低,组建网络节点数

  • 【测试】黑盒测试用例设计方法

    【测试】黑盒测试用例设计方法黑盒测试用例设计方法包括:1、等价类划分法、2、边界值分析法、3、错误推测法、4、因果图法、5、判定表驱动法、6、正交试验设计法、7、功能图法、8、场景法等。9、状态迁移法10、流程分析法11、逐级细分法12、输入域分析法13、输出域分析法14、异常分析等价类划分法概念等价类划分法是把程序的输入域划分成若干部分…

  • idea激活码2022.01【中文破解版】

    (idea激活码2022.01)本文适用于JetBrains家族所有ide,包括IntelliJidea,phpstorm,webstorm,pycharm,datagrip等。https://javaforall.cn/100143.htmlIntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,上面是详细链接哦~4…

  • #Photoshop#_pdf文档解析失败

    #Photoshop#_pdf文档解析失败来源:导入AdobePhotoshop(.psd)图像AdobePhotoshop档案格式规格:https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_89817vs2017案例

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号