redis源码分析-ziplist(压缩链表)
来源:互联网 发布:cpu跑分软件 编辑:程序博客网 时间:2024/05/15 03:41
ziplist结构在redis运用非常广泛,是列表、字典等数据类型的底层结构之一。ziplist的优点在于能够一定程度地节约内存。
ziplist构成
ziplist结构由zip_header、zip_entry、zip_end三部分组成。
ZIP_HEADER:顾名思义,压缩列表的头部。内部包含ZIP_BYTES、ZIP_TAIL、ZIP_LENGTH属性。
– ZIP_BYTES:ziplist占用的总字节数,uint32_t型;
– ZIP_TAIL:最后一个节点的偏移量,uint32_t型;
– ZIP_LENGTH:ziplist的长度,uint16_t型;
由此,我们可计算出ZIP_HEADER所占用的字节数:4(ZIP_BYTES)+4(ZIP_TAIL)+2(ZIP_LENGTH)=10.
ZIP_ENTRY : ziplist的节点信息, 节点(entry)结构定义位于ziplist.c。
typedef struct zlentry { //prevrawlensize: 前节点长度所占的字节数 //prevrawlen: 前节点的长度 unsigned int prevrawlensize, prevrawlen; //lensize: 当前节点的长度所占字节数 //len: 当前节点长度 unsigned int lensize, len; //entry: header大小 unsigned int headersize; //当前节点编码 unsigned char encoding; //指向当前节点的指针 unsigned char *p;} zlentry;
上面各属性的注释已经说明其代表的含义。可以看出zlentry的属性还是比较多的。实际上,ziplist在存储节点信息时,并没有将zlentry数据结构所有属性保存,而是做了简化。
prevlen:前节点的长度。
encoding & length : 节点的编码和长度。
节点的编码保存在第一个字节,当字节以“11”开头时,表示节点存储的数据为整型,其他情况视为字符串类型。
由于整型的长度是固定的,因此 只需存储encoding信息,length值可根据编码进行计算得出。
与整型数据相比,字符串的设计相对复杂些。尤其是字符串的长度(length)取值,设计得非常灵活、巧妙。
ZIP_END:ziplist的结束标识,ZIP_END=255。
ziplist核心函数
ziplistNew: 创建一个ziplist
#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl)))#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))#define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))#define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t))unsigned char *ziplistNew(void) { unsigned int bytes = ZIPLIST_HEADER_SIZE+1; //为zl分配空间 unsigned char *zl = zmalloc(bytes); //初始化bytes值 ZIPLIST_BYTES(zl) = intrev32ifbe(bytes); //初始化tail_offset信息 ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE); //初始化zl_length信息 ZIPLIST_LENGTH(zl) = 0; //将结束标识添加至尾部 zl[bytes-1] = ZIP_END; return zl;}
__ziplistInsert : 在指定的节点后,新增一个节点。供ziplistPush和ziplistInsert等函数内部调用。
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) { ...... //计算s的长度 if (zipTryEncoding(s,slen,&value,&encoding)) { reqlen = zipIntSize(encoding); } else { reqlen = slen; } //前节点长度所占用的字节数 //prevlen:p前置节点的长度 reqlen += zipPrevEncodeLength(NULL,prevlen); //新节点的编码及长度所占用的字节数 reqlen += zipEncodeLength(NULL,encoding,slen); //计算p前节点与新节点长度所占用的字节数之差 nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0; //重置ziplist的大小 offset = p-zl; zl = ziplistResize(zl,curlen+reqlen+nextdiff); p = zl+offset; if (p[0] != ZIP_END) { //为新节点腾出空间,向后整体移动 memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff); //将新节点的长度编码至后置节点 //p+reqlen: 定位到后置节点 zipPrevEncodeLength(p+reqlen,reqlen); // 更新到达表尾的偏移量 ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen); 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 { ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl); } //新旧节点长度所占用的字节数不一致时,则更新后面节点的信息 if (nextdiff != 0) { offset = p-zl; zl = __ziplistCascadeUpdate(zl,p+reqlen); p = zl+offset; } //记录前节点的长度 p += zipPrevEncodeLength(p,prevlen); //记录节点的编码及长度信息 p += zipEncodeLength(p,encoding,slen); if (ZIP_IS_STR(encoding)) { //保存字符串数据 memcpy(p,s,slen); } else { //保存整型数据 zipSaveInteger(p,value,encoding); } //链表长度+1 ZIPLIST_INCR_LENGTH(zl,1); return zl;}
__ziplistInsert函数的代码较多,下面给出一个简单的流程图:
与__ziplistInsert相左,ziplistDelete提供删除节点功能,实现比较简单,这里就不贴代码了。值得注意的是,与新增节点一样,删除节点时也需要更新p之后的节点前置节点长度信息
ziplist还有一个比较特别的函数:ziplistLen,用于计算ziplist的长度,前面有提到ziplist使用2个字节来保存长度信息,当ziplist长度超出2个字节所能表示的范围时,ziplist是怎么存储的?长度又该如何计算呢?
//计算ziplist的长度unsigned int ziplistLen(unsigned char *zl) { unsigned int len = 0; //当 ZIPLIST_LENGTH(zl)<65535 时,则返回其值 if (intrev16ifbe(ZIPLIST_LENGTH(zl)) < UINT16_MAX) { len = intrev16ifbe(ZIPLIST_LENGTH(zl)); } else { //当ZIPLIST_LENGTH(zl)=65535 时,需要遍历整个链表,计算其长度 unsigned char *p = zl+ZIPLIST_HEADER_SIZE; while (*p != ZIP_END) { p += zipRawEntryLength(p); len++; } /* Re-store length if small enough */ //如果长度小于65535时,更新ziplist的ZIPLIST_LENGTH值 if (len < UINT16_MAX) ZIPLIST_LENGTH(zl) = intrev16ifbe(len); } return len;}
ziplist还提供了一些方法,由于篇幅关系,这里就不一一介绍。有兴趣的童鞋可以了解下。
ziplist连锁更新
前面简单地介绍ziplist的节点构成及几个核心函数。我们知道ziplist的insert和delete节点时,系统会自动更新指定节点之后其他节点的前置节点长度信息。
当某个字符串节点的前置节点长度<254时,使用一个字节进行存储,当前置节点长度>=254,则使用5个字节。试想一下,当将一个长度>=254的节点添加到一段连续的“长度在250~253”节点之前时,此时,插入位置之后的所有节点的前置节点长度信息都将面临更新。这就是所谓的“连锁更新”现象。
下面用实例简单说明下。
1).当在entry1前插入p1时,entry1前置节点发生变化。由于P1的长度>=254,那么系统需要为entry1分配5个字节用于存储前置节点长度信息。此时, entry1长度由250变为250+5 = 255个字节。
2). 由于entry1长度发生变化(250->255),entry2之前使用一个字节存储entry1长度信息,现在无法容纳新的长度数据,需要系统扩充4个字节的容量进行存储。
3). 依此类推,直到entryn节点更新完成后停止。
总结
ziplist由header、entry、end三部分组成。支持从表头至表尾(ZIP_HEADER)和表尾至表头(ZIP_TAIL)遍历.
ziplist的长度:当zip_length<65535时,zip_length代表ziplist长度。当zip_length=65535时,需遍历整个ziplist进行计算得出。
由于ziplist每个节点保留“前节点的长度”信息,当发生insert、delete等操作时,系统会更新指定节点之后的其他节点的“前节点长度”属性。
虽然ziplist的“连锁更新”现象发生几率较小,但在使用的过程应尽量规避。
- redis源码分析-ziplist(压缩链表)
- 【redis源码分析】压缩列表---ziplist
- redis ziplist压缩列表的源码分析
- redis源码分析(八)、redis数据结构之压缩ziplist--------ziplist.c ziplist.h学习笔记
- 唯快不破:redis源码剖析05-ziplist字符串压缩链表
- Redis源码分析(六)--- ziplist压缩列表
- Redis源码分析(七)——压缩列表Ziplist
- Redis内部数据结构详解之压缩链表(ziplist)
- Redis内部数据结构详解之压缩链表(ziplist)
- 【Redis源码剖析】 - Redis内置数据结构之压缩列表ziplist
- Redis源码剖析和注释(六)--- 压缩列表(ziplist)
- redis源码 -ziplist
- Redis-数据结构-压缩列表-ziplist
- redis源码分析(三)-ziplist的实现
- Redis skip list,ziplist分析
- 结合redis设计与实现的redis源码学习-7-ziplist(压缩列表)
- redis源码系列-数据结构(adlist/ziplist/dict)
- redis结构分析——ziplist
- jquery知识点 语法
- 考上好大学,然后进入IT行业是穷人孩子晋级中产的唯一出路?
- MySQL|RESTRICT|NO ACTION|CASCADE| SET NULL四种外键约束
- 重建二叉树
- 内排序-插入类排序-希尔排序
- redis源码分析-ziplist(压缩链表)
- STM32 FATFAS的学习
- Spark学习使用笔记 - Scala篇(3)- 对象
- 【Cocos2d-x游戏开发】3.6 Win7打安卓包
- 从Windows系统服务获取活动用户的注册表信息
- hdu 1226超级密码
- Web开发中基础常识详解-Js,JSP,tomcat,Servlet,web是干什么的?
- 集合详解(四)----HashSet和HashMap源码剖析(JDK1.7)
- Android资源解析之mipmap