ize(zl,curlen+reqlen+nextdiff);
p = zl+offset;
/* Apply memory move when necessary and update tail offset. */
if (p[0] != ZIP_END) {
/* Subtract one because of the ZIP_END bytes */
//将原有从p-nextdiff开始全部向后偏移,余留出reqlen保存即将insert的数据
/**
nextdiff = -4:原来p有5个字节来存储上个节点的长度,而现在只需要1个,
因此只需要将p+4后面的字节偏移到p+reqlen即可,这样就
只保留1个字节保存reqlen的长度了
nextdiff = 4: 原来p只有1个字节来存储上个节点的长度,现在需要5个,
那就将p-4后面的字节偏移到p+reqlen,这样p原来有1个字节
加上多偏移来的4个字节就可以保存reqlen的长度了
nextdiff = 0: 不需要考虑
*/
memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);
/* Encode this entry's raw length in the next entry. */
zipPrevEncodeLength(p+reqlen,reqlen);//下个节点保存即将insert数据的所占字节数
/* 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 也加上到 zltail 上
tail = zipEntry(p+reqlen);
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 的 zltail 属性,现在新添加节点为表尾节点
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,说明现在的p+reqlen节点的长度变了,需要级联更新下个节点能否保存
p+reqlen节点的长度值
*/
if (nextdiff != 0) {
offset = p-zl;
zl = __ziplistCascadeUpdate(zl,p+reqlen);
p = zl+offset;
}
/* Write the entry */
p += zipPrevEncodeLength(p,prevlen);//填写保存上一个节点长度的字节数
p += zipEncodeLength(p,encoding,slen);//填写保存当前节点长度的字节数
if (ZIP_IS_STR(encoding)) {//保存当前节点的字符串
memcpy(p,s,slen);
} else {
zipSaveInteger(p,value,encoding);//整数
}
ZIPLIST_INCR_LENGTH(zl,1);//length + 1
return zl;
}
ziplist剩余的一些函数就不一一列举分析,理解上述三个函数,其余都很简单,关注这三个函数的重点是作者在realloc之后如何通过memmove保证后续节点的数据保持不变的,不得不说Redis的作者对ziplist的实现很让人佩服。
另外,推荐Redis黄健宏(huangz1990)的Redis设计与实现一书中有关于ziplist插入与删除节点的简单模拟实现。
九、小结
ziplist的最大的好处就是相比skiplist节约大量内存,但是在插入、删除、查询等操作上的时间复杂度与skiplist都是无法比拟的,当保存的数据比较少时,操作的时间自然可以接受,内存就是关键因素。
ziplist在Redis中主要用于Hash Table、List、Sorted Set数据类型小范围数据的存储,本文描述的ziplist的存储都是无序的,如何实现在Sorted Set中的有序存储待以后分析,无序变有序无疑又增加的时间复杂度。
总之,ziplist就是一种用时间换空间的策略。
最后感谢黄健宏(huangz1990)的Redis设计与实现及其他对Redis2.6源码的相关注释对我在研究Redis2.8源码方面的帮助。
由于本文其他有关ziplist的函数没有分析,包括本文内容有问题欢迎评论,有些代码我也不是特别的明白,谢谢。