压缩列表的具体数据结构如下:

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

其中zlbytes为4字节大小,代表这个列表的大小。

zltail为4字节大小,为最后一个entry的地址。

zllen为2字节大小,代表列表中entry的总个数。

而后为存放列表中实际数据的enrty。

最后则是1个字节的zlend,代表列表的结尾,其直接被赋值为255,为了防止普通entry混淆,直接设为1字节的255,其他entry都不会以单字节的255进行开头,达到列表结束的快速确认的目的。

在列表中部的entry由zlentry构成。

typedef struct zlentry {unsigned int prevrawlensize; /* Bytes used to encode the previous entry len*/unsigned int prevrawlen;     /* Previous entry len. */unsigned int lensize;        /* Bytes used to encode this entry type/len.For example strings have a 1, 2 or 5 bytesheader. Integers always use a single byte.*/unsigned int len;            /* Bytes used to represent the actual entry.For strings this is just the string lengthwhile for integers it is 1, 2, 3, 4, 8 or0 (for 4 bit immediate) depending on thenumber range. */unsigned int headersize;     /* prevrawlensize + lensize. */unsigned char encoding;      /* Set to ZIP_STR_* or ZIP_INT_* depending onthe entry encoding. However for 4 bitsimmediate integers this can assume a rangeof values and must be range-checked. */unsigned char *p;            /* Pointer to the very start of the entry, thatis, this points to prev-entry-len field. */
} zlentry;

其中prevrawlen代表前一个entry节点的长度,而prevrawlensize则代表prevrawlen的编码大小。而后len则表示当前整个entry节点的实际长度,lensize则是前者的编码大小。之后的headsize则为prevrawlensize和lensize大小之和。encoding为当前entry节点具体存储内容的编码大小,而*p则是存储内容的起始位置。

unsigned char *ziplistNew(void) {unsigned int bytes = ZIPLIST_HEADER_SIZE+1;unsigned char *zl = zmalloc(bytes);ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);ZIPLIST_LENGTH(zl) = 0;zl[bytes-1] = ZIP_END;return zl;
}

ziplistNew()函数则是建立新的压缩列表的函数。

空的压缩列表需要zlbytes,zltail,zllen,zlend存在。以上之和为11字节。所以一个空的压缩列表至少需要11字节,首先会申请11字节的空间。

/* Return total bytes a ziplist is composed of. */
#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))/* Return the offset of the last item inside the ziplist. */
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))/* Return the length of a ziplist, or UINT16_MAX if the length cannot be* determined without scanning the whole ziplist. */
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))

之后一次初始化前10个字节位置的数据,接着赋最后一字节为255,整个空的压缩列表初试化结束。

插入到压缩列表的函数则为__ziplistInsert()函数。

整个函数比较长,其函数签名如下。

unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen);

第一个参数zl为要插入数据的目标压缩列表,p为插入目标位置,s为插入内容,slen为插入内容的长度。

首先根据插入位置p得到前一个entry节点的长度。

 if (p[0] != ZIP_END) {ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);} else {unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);if (ptail[0] != ZIP_END) {prevlen = zipRawEntryLength(ptail);}
}

此处如果p的第一个字节不是255,则说明插入位置并不是列表末尾,那么需要得到前一个节点的长度。需要通过当前p位置的节点的头信息里面去读取。

#define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do {                          \if ((ptr)[0] < ZIP_BIG_PREVLEN) {                                          \(prevlensize) = 1;                                                     \} else {                                                                   \(prevlensize) = 5;                                                     \}                                                                          \
} while(0);

在压缩列表中,当列表长度小于254时,列表长度将会以1字节大小存放,如果大于等于254,将会转为5字节来存放列表长度,其中第一字节为254,后面4个字节存放具体大小。

#define ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen) do {                     \ZIP_DECODE_PREVLENSIZE(ptr, prevlensize);                                  \if ((prevlensize) == 1) {                                                  \(prevlen) = (ptr)[0];                                                  \} else if ((prevlensize) == 5) {                                           \assert(sizeof((prevlen)) == 4);                                    \memcpy(&(prevlen), ((char*)(ptr)) + 1, 4);                             \memrev32ifbe(&prevlen);                                                \}                                                                          \
} while(0);

在完成上一个节点长度的获取之后。

  /* See if the entry can be encoded */if (zipTryEncoding(s,slen,&value,&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;}

试着去对需要存放的数据进行编码。

int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding) {long long value;if (entrylen >= 32 || entrylen == 0) return 0;if (string2ll((char*)entry,entrylen,&value)) {/* Great, the string can be encoded. Check what's the smallest* of our encoding types that can hold this value. */if (value >= 0 && value <= 12) {*encoding = ZIP_INT_IMM_MIN+value;} else if (value >= INT8_MIN && value <= INT8_MAX) {*encoding = ZIP_INT_8B;} else if (value >= INT16_MIN && value <= INT16_MAX) {*encoding = ZIP_INT_16B;} else if (value >= INT24_MIN && value <= INT24_MAX) {*encoding = ZIP_INT_24B;} else if (value >= INT32_MIN && value <= INT32_MAX) {*encoding = ZIP_INT_32B;} else {*encoding = ZIP_INT_64B;}*v = value;return 1;}return 0;
}

此处对于string类型要存放的数据尝试进行压缩,如果本身长度已经超过32,那么不进行操作,否则根据string转为long long类型的结果进行压缩编码。

 reqlen += zipStorePrevEntryLength(NULL,prevlen);reqlen += zipStoreEntryEncoding(NULL,encoding,slen);

之后,reqlen将会计算根据前一节点长度与具体存放数据的字节大小之和,作为新插入节点下一个节点的起始位置。

unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) {if (p == NULL) {return (len < ZIP_BIG_PREVLEN) ? 1 : sizeof(len)+1;} else {if (len < ZIP_BIG_PREVLEN) {p[0] = len;return 1;} else {return zipStorePrevEntryLengthLarge(p,len);}}
}
int zipStorePrevEntryLengthLarge(unsigned char *p, unsigned int len) {if (p != NULL) {p[0] = ZIP_BIG_PREVLEN;memcpy(p+1,&len,sizeof(len));memrev32ifbe(p+1);}return 1+sizeof(len);
} 

如果是前面的长度小于254,那么将会作为一个字节,如果大于等于254,那么则是5字节。

 int forcelarge = 0;nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;if (nextdiff == -4 && reqlen < 4) {nextdiff = 0;forcelarge = 1;}

如果在插入之后,同时需要保证插入之后的下一个节点保存前一个位置节点长度大小的字节数足够存放新插入节点长度的大小。在这里如果原本的大小是5字节,而新的是1字节,那么不用发生变化。

offset = p-zl;
zl = ziplistResize(zl,curlen+reqlen+nextdiff);
p = zl+offset;

之后需要重新计算偏移量,由于在插入的过程中,插入位置p之前的偏移量不会发生变化,则需要保存。之后会重新申请长度,并把新的列表地址与偏移量得到新的插入位置的起点。

if (p[0] != ZIP_END) {/* Subtract one because of the ZIP_END bytes */memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);/* Encode this entry's raw length in the next entry. */if (forcelarge)zipStorePrevEntryLengthLarge(p+reqlen,reqlen);elsezipStorePrevEntryLength(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. */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);}

之后由于p+reqlen为下一个节点的起始位置,那么就将新节点之后的数据赋值到目标位置。

之后,如果之前forcelarge为1,说说明新的节点大小需要强制扩展为4个字节,以适应原本的节点表示上一节点的长度大小。

之后更新压缩列表这项队尾的指针,如果因为下一节点的长度也发生变化,需要把这情况也考虑进去。

如果要插入的新及节点本身就是最后一个节点,只要简单的将指向队尾的指针指导其插入的位置即可。

if (nextdiff != 0) {offset = p-zl;zl = __ziplistCascadeUpdate(zl,p+reqlen);p = zl+offset;}

如果下一个节点的长度由于新节点的插入,而需要增加的四个字节也正好超过了254大小,那么也需要将其下一个节点连锁增加,直到列表中下一个不需要增加长度的节点出现为止。

unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), rawlen, rawlensize;size_t offset, noffset, extra;unsigned char *np;zlentry cur, next;while (p[0] != ZIP_END) {zipEntry(p, &cur);rawlen = cur.headersize + cur.len;rawlensize = zipStorePrevEntryLength(NULL,rawlen);/* Abort if there is no next entry. */if (p[rawlen] == ZIP_END) break;zipEntry(p+rawlen, &next);/* Abort when "prevlen" has not changed. */if (next.prevrawlen == rawlen) break;if (next.prevrawlensize < rawlensize) {/* The "prevlen" field of "next" needs more bytes to hold* the raw length of "cur". */offset = p-zl;extra = rawlensize-next.prevrawlensize;zl = ziplistResize(zl,curlen+extra);p = zl+offset;/* Current pointer and offset for next element. */np = p+rawlen;noffset = np-zl;/* Update tail offset when next element is not the tail element. */if ((zl+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) != np) {ZIPLIST_TAIL_OFFSET(zl) =intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra);}/* Move the tail to the back. */memmove(np+rawlensize,np+next.prevrawlensize,curlen-noffset-next.prevrawlensize-1);zipStorePrevEntryLength(np,rawlen);/* Advance the cursor */p += rawlen;curlen += extra;} else {if (next.prevrawlensize > rawlensize) {/* This would result in shrinking, which we want to avoid.* So, set "rawlen" in the available bytes. */zipStorePrevEntryLengthLarge(p+rawlen,rawlen);} else {zipStorePrevEntryLength(p+rawlen,rawlen);}/* Stop here, as the raw length of "next" has not changed. */break;}}return zl;
}

__ziplistCascadUpdate()函数正式实现了从前往后检查是否需要连锁扩容,并根据需要继续扩容的函数。

 /* Write the entry */p += zipStorePrevEntryLength(p,prevlen);p += zipStoreEntryEncoding(p,encoding,slen);if (ZIP_IS_STR(encoding)) {memcpy(p,s,slen);} else {zipSaveInteger(p,value,encoding);}ZIPLIST_INCR_LENGTH(zl,1);return zl;

最后将要插入的数据复制到相应的位置,压缩列表的插入宣告完毕。

redis的压缩列表源码ziplist解析相关推荐

  1. 霸榜巨作、阿里内部顶级专家整理(Redis 5设计与源码分析)

    前言 在开源界,高性能服务的典型代表就是Nginx和Redis.纵观这两个软件的源码,都是非常简洁高效的,也都是基于异步网络I/O机制的,所以对于要学习高性能服务的程序员或者爱好者来说,研究这两个网络 ...

  2. 新书推荐 |《Redis 5设计与源码分析》

    新书推荐 <Redis 5设计与源码分析> 点击上图了解及购买 好未来.滴滴.百度等公司专家联合撰写,掌握Redis 5设计与命令实现,透彻掌握分布式缓存. 编辑推荐 多名专家联袂推荐,资 ...

  3. Go netpoll I/O 多路复用构建原生网络模型之源码深度解析

    原文 Go netpoll I/O 多路复用构建原生网络模型之源码深度解析 导言 Go 基于 I/O multiplexing 和 goroutine 构建了一个简洁而高性能的原生网络模型(基于 Go ...

  4. 【笔记-vue】《imooc-vue.js高仿饿了么》、《imooc-vue 音乐app》、《imooc-vue.js源码全方位解析》

    20170709 - 20171128:<imooc-vue.js高仿饿了么> 一.第一章 课程简介 1-1课程简介 1.需求分析-脚手架工具-数据mock-架构设计-代码编写-自测-编译 ...

  5. 【TarsosDSP】TarsosDSP 简介 ( TarsosDSP 功能 | 相关链接 | 源码和相关资源收集 | TarsosDSP 示例应用 | TarsosDSP 源码路径解析 )

    文章目录 I . TarsosDSP 函数库简介 II . TarsosDSP 功能 III . TarsosDSP 相关资源链接 ( 官方资料 ) IV . TarsosDSP 源码和相关资源收集 ...

  6. 【作者面对面问答】包邮送《Redis 5设计与源码分析》5本

    墨墨导读:本文节选自<Redis 5设计与源码分析>,主要为读者分析Redis高性能内幕,重点从源码层次讲解了Redis事件模型,网络IO事件重在使用IO复用模型,时间事件重在限制最大执行 ...

  7. 选redis还是memcache,源码怎么说?

    选redis还是memcache,源码怎么说? memcache和redis是互联网分层架构中,最常用的KV缓存.不少同学在选型的时候会纠结,到底是选择memcache还是redis. 画外音:不鼓励 ...

  8. Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析

      我们在看Spring Boot源码时,经常会看到一些配置类中使用了注解,本身配置类的逻辑就比较复杂了,再加上一些注解在里面,让我们阅读源码更加难解释了,因此,这篇博客主要对配置类上的一些注解的使用 ...

  9. 《Spring源码深度解析 郝佳 第2版》bean的加载、循环依赖的解决

    往期博客: <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 往期博客完成了xml文件加载 ...

最新文章

  1. 时间序列(四)ARIMA模型与差分
  2. 来,加入前端自动化单元测试
  3. laravel token ajax,Laravel中ajax post操作需要传递csrf token的最优化解决方式
  4. 模拟FCFS调度算法(先来先服务)没错,是篇好文章!
  5. android 介绍0
  6. SQL JOIN\SQL INNER JOIN 关键字\SQL LEFT JOIN 关键字\SQL RIGHT JOIN 关键字\SQL FULL JOIN 关键字...
  7. 覆盖所有面试知识点,持续更新中
  8. 列注释_机器学习 Pandas 03:基础 前16题 ( 带答案、注释 )
  9. PyTorch搜索Tensor指定维度的前K大个(K小个)元素--------(torch.topk)命令参数详解及举例
  10. C#串口数据读写——计数器数据获取重置功能记录
  11. c 语言游戏代码大全,C语言经典游戏代码
  12. [渝粤教育] 四川农业大学 农业气象学 参考 资料
  13. 开源远程桌面软件_RustDesk_(可自建远程桌面服务器)
  14. 转载_ANC降噪学习
  15. css空心三角形_CSS实现空心三角指示箭头
  16. 电脑鼠标左键按下去没反应怎么办
  17. 大数据开发工程师目录
  18. 例行性工作排程 (crontab)
  19. 提取TCGA 中体细胞突变数据的表达矩阵
  20. Informatica使用操作流程--缓慢变化维 案例9

热门文章

  1. 原生H5 select自动提示搜索
  2. HDOJ 2602-Bone Collector(0/1背包模板、打印方案及滚动数组解法)
  3. linux 如何解压.exe,linux下解压火狐浏览器压缩包 ./filefox 运行可执行程序报错问题...
  4. mysql语句engin_MySQL必会的SQL语句
  5. 基于prometheus + grafana + mysql + Telegram 监控告警
  6. linux,shell中if else if的写法,if elif
  7. 获得手机屏幕相关参数
  8. SGU 222 Little Rooks
  9. 转载------工作10年的人总结的6句话
  10. 怎样在DOS下查看屏蔽和开启端口了