一.压缩列表ziplist在redis中的应用
1.做列表键
当一个列表键只包含少量列表项,并且每个列表项要么是小整数,要么是短字符串,那么redis会使用压缩列表作为列表键的底层实现
2.哈希键
当一个哈希键只包含少量的键值对,并且每个键值对的键和值要么是小整数,要么是短字符串,那么redis会使用压缩列表作为哈希键的底层实现
二.压缩列表的定义:
压缩列表ziplist是redis为了节约内存而开发的,是由一些了特殊编码的连续内存块组成的顺序数据结构
三.关于压缩列表的连锁更新
更新的是什么?
更新的是每个结点的previous_entry_length以及结点的内存中的位置,涉及到内存空间的重分配
连锁更新发生的情况有哪些?
比如在一个ziplist中,有多个连续的,长度介于250字节到253字节之间的结点e1到eN,因为e1到eN的所有结点都小于254字节,所以记录这些结点的privious_entry_length属性仅仅需要一个字节,这个时候如果我们将一个长度大于254字节的新结点插入到ziplist的头部,那么e1到eN结点是所有privoius_entry_length属性所占字节要从1字节变成5个字节,这里面涉及到对所有结点内存空间的重新分配,在这种最坏的情况下,需要对ziplist执行N次的空间重新分配操作,每次空间重分配操作的复杂度为O(N),所以连锁更新最坏的情况下,复杂度为O(N*N)
尽管连锁更新的复杂度高,但是真正造成性能问题的概率还是很低的,因为连锁更新的极端情况并不少见,在结点数量少的时候,即使出现连锁更新,也不会对性能造成影响,基于以上原因
ziplist的平均复杂度仅为O(N)
源码分析:
ziplist.h
#define ZIPLIST_HEAD 0
#define ZIPLIST_TAIL 1 /*
前置说明: ziplist定义成了宏属性,在ziplist.c文件中
ziplist的结点zlentry的结构体也在ziplist.c文件中
看不懂为什么这么操作.....好迷啊 */ //========= API的定义 =====================// //创建一个新的压缩列表
unsigned char *ziplistNew(void); //创建一个包含给定值的新结点,并将这个新结点加到压缩列表的头或尾
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, unsigned 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);
ziplist.c
/*
* Ziplist 是为了尽可能地节约内存而设计的特殊编码双端链表。
*
* Ziplist 可以储存字符串值和整数值,
* 其中,整数值被保存为实际的整数,而不是字符数组。
*
* Ziplist 允许在列表的两端进行 O(1) 复杂度的 push 和 pop 操作。
* 但是,因为这些操作都需要对整个 ziplist 进行内存重分配,
* 所以实际的复杂度和 ziplist 占用的内存大小有关。
*-------------------------------------------------------------------------------------------------------------------------------------------------------------
* Ziplist 的整体布局:
*
* 以下是 ziplist 的一般布局:
*
* <zlbytes><zltail><zllen><entry><entry><zlend>
*
* <zlbytes> 是一个无符号整数,保存着 ziplist 使用的内存数量。
* 通过这个值,程序可以直接对 ziplist 的内存大小进行调整,
* 而无须为了计算 ziplist 的内存大小而遍历整个列表。
*
* <zltail> 保存着到达列表中最后一个节点的偏移量。
* 这个偏移量使得对表尾的 pop 操作可以在无须遍历整个列表的情况下进行。
*
* <zllen> 保存着列表中的节点数量。
* 当 zllen 保存的值大于 2**16-2 时,
* 程序需要遍历整个列表才能知道列表实际包含了多少个节点。
*
* <zlend> 的长度为 1 字节,值为 255 ,标识列表的末尾。
*
* ZIPLIST ENTRIES:
* ZIPLIST 节点:
*
* 每个 ziplist 节点的前面都带有一个 header ,这个 header 包含两部分信息:
* 1)前置节点的长度,在程序从后向前遍历时使用。
* 2)当前节点所保存的值的类型和长度。
*
* 编码前置节点的长度的方法如下:
*
* 1) 如果前置节点的长度小于 254 字节,那么程序将使用 1 个字节来保存这个长度值。
*
* 2) 如果前置节点的长度大于等于 254 字节,那么程序将使用 5 个字节来保存这个长度值:
* a) 第 1 个字节的值将被设为 254 ,用于标识这是一个 5 字节长的长度值。
* b) 之后的 4 个字节则用于保存前置节点的实际长度。
*
* header 另一部分的内容和节点所保存的值有关。
*
* 1) 如果节点保存的是字符串值,
* 那么这部分 header 的头 2 个位将保存编码字符串长度所使用的类型,
* 而之后跟着的内容则是字符串的实际长度。
*
* |00pppppp| - 1 byte
* String value with length less than or equal to 63 bytes (6 bits).
* 字符串的长度小于或等于 63 字节。
* |01pppppp|qqqqqqqq| - 2 bytes
* String value with length less than or equal to 16383 bytes (14 bits).
* 字符串的长度小于或等于 16383 字节。
* |10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes
* String value with length greater than or equal to 16384 bytes.
* 字符串的长度大于或等于 16384 字节。
*
* 2) 如果节点保存的是整数值,
* 那么这部分 header 的头 2 位都将被设置为 1 ,
* 而之后跟着的 2 位则用于标识节点所保存的整数的类型。
*
* |11000000| - 1 byte
* Integer encoded as int16_t (2 bytes).
* 节点的值为 int16_t 类型的整数,长度为 2 字节。
* |11010000| - 1 byte
* Integer encoded as int32_t (4 bytes).
* 节点的值为 int32_t 类型的整数,长度为 4 字节。
* |11100000| - 1 byte
* Integer encoded as int64_t (8 bytes).
* 节点的值为 int64_t 类型的整数,长度为 8 字节。
* |11110000| - 1 byte
* Integer encoded as 24 bit signed (3 bytes).
* 节点的值为 24 位(3 字节)长的整数。
* |11111110| - 1 byte
* Integer encoded as 8 bit signed (1 byte).
* 节点的值为 8 位(1 字节)长的整数。
* |1111xxxx| - (with xxxx between 0000 and 1101) immediate 4 bit integer.
* Unsigned integer from 0 to 12. The encoded value is actually from
* 1 to 13 because 0000 and 1111 can not be used, so 1 should be
* subtracted from the encoded 4 bit value to obtain the right value.
* 节点的值为介于 0 至 12 之间的无符号整数。
* 因为 0000 和 1111 都不能使用,所以位的实际值将是 1 至 13 。
* 程序在取得这 4 个位的值之后,还需要减去 1 ,才能计算出正确的值。
* 比如说,如果位的值为 0001 = 1 ,那么程序返回的值将是 1 - 1 = 0 。
* |11111111| - End of ziplist.
* ziplist 的结尾标识
*
*
* 所有整数都表示为小端字节序。
*
*/ #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>
#include "zmalloc.h"
#include "util.h"
#include "ziplist.h"
#include "endianconv.h"
#include "redisassert.h" /*
* ziplist 末端标识符,以及 5 字节长长度标识符
*/
#define ZIP_END 255
#define ZIP_BIGLEN 254 /*
* 字符串编码和整数编码的掩码
*/
#define ZIP_STR_MASK 0xc0
#define ZIP_INT_MASK 0x30 /*
* 字符串编码类型
*/
#define ZIP_STR_06B (0 << 6)
#define ZIP_STR_14B (1 << 6)
#define ZIP_STR_32B (2 << 6) /*
* 整数编码类型
*/
#define ZIP_INT_16B (0xc0 | 0<<4)
#define ZIP_INT_32B (0xc0 | 1<<4)
#define ZIP_INT_64B (0xc0 | 2<<4)
#define ZIP_INT_24B (0xc0 | 3<<4)
#define ZIP_INT_8B 0xfe /*
* 4 位整数编码的掩码和类型
*/
#define ZIP_INT_IMM_MASK 0x0f
#define ZIP_INT_IMM_MIN 0xf1 /* 11110001 */
#define ZIP_INT_IMM_MAX 0xfd /* 11111101 */
#define ZIP_INT_IMM_VAL(v) (v & ZIP_INT_IMM_MASK) /*
* 24 位整数的最大值和最小值
*/
#define INT24_MAX 0x7fffff
#define INT24_MIN (-INT24_MAX - 1) /*
* 查看给定编码 enc 是否字符串编码
*/
#define ZIP_IS_STR(enc) (((enc) & ZIP_STR_MASK) < ZIP_STR_MASK) /*
* ziplist 属性宏
*/
// 定位到 ziplist 的 bytes 属性,该属性记录了整个 ziplist 所占用的内存字节数
// 用于取出 bytes 属性的现有值,或者为 bytes 属性赋予新值
#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl)))
// 定位到 ziplist 的 offset 属性,该属性记录了到达表尾节点的偏移量
// 用于取出 offset 属性的现有值,或者为 offset 属性赋予新值
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
// 定位到 ziplist 的 length 属性,该属性记录了 ziplist 包含的节点数量
// 用于取出 length 属性的现有值,或者为 length 属性赋予新值
#define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
// 返回 ziplist 表头的大小
#define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t))
// 返回指向 ziplist 第一个节点(的起始位置)的指针
#define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE)
// 返回指向 ziplist 最后一个节点(的起始位置)的指针
#define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
// 返回指向 ziplist 末端 ZIP_END (的起始位置)的指针
#define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1) /*
空白 ziplist 示例图 area |<---- ziplist header ---->|<-- end -->| size 4 bytes 4 bytes 2 bytes 1 byte
+---------+--------+-------+-----------+
component | zlbytes | zltail | zllen | zlend |
| | | | |
value | 1011 | 1010 | 0 | 1111 1111 |
+---------+--------+-------+-----------+
^
|
ZIPLIST_ENTRY_HEAD
&
address ZIPLIST_ENTRY_TAIL
&
ZIPLIST_ENTRY_END 非空 ziplist 示例图 area |<---- ziplist header ---->|<----------- entries ------------->|<-end->| size 4 bytes 4 bytes 2 bytes ? ? ? ? 1 byte
+---------+--------+-------+--------+--------+--------+--------+-------+
component | zlbytes | zltail | zllen | entry1 | entry2 | ... | entryN | zlend |
+---------+--------+-------+--------+--------+--------+--------+-------+
^ ^ ^
address | | |
ZIPLIST_ENTRY_HEAD | ZIPLIST_ENTRY_END
|
ZIPLIST_ENTRY_TAIL
*/ /*
* 增加 ziplist 的节点数
*
* T = O(1)
*/
#define ZIPLIST_INCR_LENGTH(zl,incr) { \
if (ZIPLIST_LENGTH(zl) < UINT16_MAX) \
ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))+incr); \
} /*
* 保存 ziplist 节点信息的结构
*/
typedef struct zlentry
{ //前置节点的长度
unsigned int prevrawlen; //编码 prevrawlen 所需的字节大小
unsigned int prevrawlensize; //当前节点值的长度
unsigned int len; //编码 len 所需的字节大小
unsigned int lensize // 当前节点 header 的大小,等于 prevrawlensize + lensize
unsigned int headersize; // 当前节点值所使用的编码类型
unsigned char encoding; // 指向当前节点的指针
unsigned char *p; } zlentry; /*
* 从 ptr 中取出节点值的编码类型,并将它保存到 encoding 变量中。
*
* T = O(1)
*/
#define ZIP_ENTRY_ENCODING(ptr, encoding) do { \
(encoding) = (ptr[]); \
if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; \
} while() /*
* 返回保存 encoding 编码的值所需的字节数量
*
* T = O(1)
*/
static unsigned int zipIntSize(unsigned char encoding)
{ switch(encoding)
{
case ZIP_INT_8B:
return ;
case ZIP_INT_16B:
return ;
case ZIP_INT_24B:
return ;
case ZIP_INT_32B:
return ;
case ZIP_INT_64B:
return ;
default:
return ; /* 4 bit immediate */
} assert(NULL);
return ;
} /*
* 编码节点长度值 l ,并将它写入到 p 中,然后返回编码 l 所需的字节数量。
*
* 如果 p 为 NULL ,那么仅返回编码 l 所需的字节数量,不进行写入。
*
* T = O(1)
*/
static unsigned int zipEncodeLength(unsigned char *p, unsigned char encoding, unsigned int rawlen)
{
unsigned char len = , buf[]; // 编码字符串
if (ZIP_IS_STR(encoding))
{
/* Although encoding is given it may not be set for strings,
* so we determine it here using the raw length. */
if (rawlen <= 0x3f)
{
if (!p) return len;
buf[] = ZIP_STR_06B | rawlen;
}
else if (rawlen <= 0x3fff)
{
len += ;
if (!p) return len;
buf[] = ZIP_STR_14B | ((rawlen >> ) & 0x3f);
buf[] = rawlen & 0xff;
}
else
{
len += ;
if (!p) return len;
buf[] = ZIP_STR_32B;
buf[] = (rawlen >> ) & 0xff;
buf[] = (rawlen >> ) & 0xff;
buf[] = (rawlen >> ) & 0xff;
buf[] = rawlen & 0xff;
} // 编码整数
}
else
{
/* Implies integer encoding, so length is always 1. */
if (!p) return len;
buf[] = encoding;
} /* Store this length at p */
// 将编码后的长度写入 p
memcpy(p,buf,len); // 返回编码所需的字节数
return len;
} /*
* 解码 ptr 指针,取出列表节点的相关信息,并将它们保存在以下变量中:
*
* - encoding 保存节点值的编码类型。
*
* - lensize 保存编码节点长度所需的字节数。
*
* - len 保存节点的长度。
*
* T = O(1)
*/
#define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do { \
\
/* 取出值的编码类型 */ \
ZIP_ENTRY_ENCODING((ptr), (encoding)); \
\
/* 字符串编码 */ \
if ((encoding) < ZIP_STR_MASK) { \
if ((encoding) == ZIP_STR_06B) { \
(lensize) = ; \
(len) = (ptr)[] & 0x3f; \
} else if ((encoding) == ZIP_STR_14B) { \
(lensize) = ; \
(len) = (((ptr)[] & 0x3f) << ) | (ptr)[]; \
} else if (encoding == ZIP_STR_32B) { \
(lensize) = ; \
(len) = ((ptr)[] << ) | \
((ptr)[] << ) | \
((ptr)[] << ) | \
((ptr)[]); \
} else { \
assert(NULL); \
} \
\
/* 整数编码 */ \
} else { \
(lensize) = ; \
(len) = zipIntSize(encoding); \
} \
} while(); /*
* 对前置节点的长度 len 进行编码,并将它写入到 p 中,
* 然后返回编码 len 所需的字节数量。
*
* 如果 p 为 NULL ,那么不进行写入,仅返回编码 len 所需的字节数量。
*
* T = O(1)
*/
static unsigned int zipPrevEncodeLength(unsigned char *p, unsigned int len)
{ // 仅返回编码 len 所需的字节数量
if (p == NULL)
{
return (len < ZIP_BIGLEN) ? : sizeof(len)+; // 写入并返回编码 len 所需的字节数量
}
else
{ // 1 字节
if (len < ZIP_BIGLEN)
{
p[] = len;
return ; // 5 字节
}
else
{
// 添加 5 字节长度标识
p[] = ZIP_BIGLEN;
// 写入编码
memcpy(p+,&len,sizeof(len));
// 如果有必要的话,进行大小端转换
memrev32ifbe(p+);
// 返回编码长度
return +sizeof(len);
}
}
} /*
*
* 将原本只需要 1 个字节来保存的前置节点长度 len 编码至一个 5 字节长的 header 中。
*
* T = O(1)
*/
static void zipPrevEncodeLengthForceLarge(unsigned char *p, unsigned int len)
{ if (p == NULL) return; // 设置 5 字节长度标识
p[] = ZIP_BIGLEN; // 写入 len
memcpy(p+,&len,sizeof(len));
memrev32ifbe(p+);
} /*
* 解码 ptr 指针,
* 取出编码前置节点长度所需的字节数,并将它保存到 prevlensize 变量中。
*
* T = O(1)
*/
#define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do { \
if ((ptr)[] < ZIP_BIGLEN) { \
(prevlensize) = ; \
} else { \
(prevlensize) = ; \
} \
} while(); /*
* 解码 ptr 指针,
* 取出编码前置节点长度所需的字节数,
* 并将这个字节数保存到 prevlensize 中。
*
* 然后根据 prevlensize ,从 ptr 中取出前置节点的长度值,
* 并将这个长度值保存到 prevlen 变量中。
*
* T = O(1)
*/
#define ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen) do { \
\
/* 先计算被编码长度值的字节数 */ \
ZIP_DECODE_PREVLENSIZE(ptr, prevlensize); \
\
/* 再根据编码字节数来取出长度值 */ \
if ((prevlensize) == ) { \
(prevlen) = (ptr)[]; \
} else if ((prevlensize) == ) { \
assert(sizeof((prevlensize)) == ); \
memcpy(&(prevlen), ((char*)(ptr)) + , ); \
memrev32ifbe(&prevlen); \
} \
} while(); /*
* 计算编码新的前置节点长度 len 所需的字节数,
* 减去编码 p 原来的前置节点长度所需的字节数之差。
*
* T = O(1)
*/
static int zipPrevLenByteDiff(unsigned char *p, unsigned int len)
{
unsigned int prevlensize; // 取出编码原来的前置节点长度所需的字节数
// T = O(1)
ZIP_DECODE_PREVLENSIZE(p, prevlensize); // 计算编码 len 所需的字节数,然后进行减法运算
// T = O(1)
return zipPrevEncodeLength(NULL, len) - prevlensize;
} /*
* 返回指针 p 所指向的节点占用的字节数总和。
*
* T = O(1)
*/
static unsigned int zipRawEntryLength(unsigned char *p)
{
unsigned int prevlensize, encoding, lensize, len; // 取出编码前置节点的长度所需的字节数
// T = O(1)
ZIP_DECODE_PREVLENSIZE(p, prevlensize); // 取出当前节点值的编码类型,编码节点值长度所需的字节数,以及节点值的长度
// T = O(1)
ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len); // 计算节点占用的字节数总和
return prevlensize + lensize + len;
} /* Check if string pointed to by 'entry' can be encoded as an integer.
* Stores the integer value in 'v' and its encoding in 'encoding'.
*
* 检查 entry 中指向的字符串能否被编码为整数。
*
* 如果可以的话,
* 将编码后的整数保存在指针 v 的值中,并将编码的方式保存在指针 encoding 的值中。
*
* 注意,这里的 entry 和前面代表节点的 entry 不是一个意思。
*
* T = O(N)
*/
static int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding)
{
long long value; // 忽略太长或太短的字符串
if (entrylen >= || entrylen == ) return ; // 尝试转换
// T = O(N)
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. */
// 转换成功,以从小到大的顺序检查适合值 value 的编码方式
if (value >= && value <= )
{
*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 ;
} // 转换失败
return ;
} /* Store integer 'value' at 'p', encoded as 'encoding'
*
* 以 encoding 指定的编码方式,将整数值 value 写入到 p 。
*
* T = O(1)
*/
static void zipSaveInteger(unsigned char *p, int64_t value, unsigned char encoding)
{
int16_t i16;
int32_t i32;
int64_t i64; if (encoding == ZIP_INT_8B)
{
((int8_t*)p)[] = (int8_t)value;
}
else if (encoding == ZIP_INT_16B)
{
i16 = value;
memcpy(p,&i16,sizeof(i16));
memrev16ifbe(p);
}
else if (encoding == ZIP_INT_24B)
{
i32 = value<<;
memrev32ifbe(&i32);
memcpy(p,((uint8_t*)&i32)+,sizeof(i32)-sizeof(uint8_t));
}
else if (encoding == ZIP_INT_32B)
{
i32 = value;
memcpy(p,&i32,sizeof(i32));
memrev32ifbe(p);
}
else if (encoding == ZIP_INT_64B)
{
i64 = value;
memcpy(p,&i64,sizeof(i64));
memrev64ifbe(p);
}
else if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX)
{
/* Nothing to do, the value is stored in the encoding itself. */
}
else
{
assert(NULL);
}
} /* Read integer encoded as 'encoding' from 'p'
*
* 以 encoding 指定的编码方式,读取并返回指针 p 中的整数值。
*
* T = O(1)
*/
static int64_t zipLoadInteger(unsigned char *p, unsigned char encoding)
{
int16_t i16;
int32_t i32;
int64_t i64, ret = ; if (encoding == ZIP_INT_8B)
{
ret = ((int8_t*)p)[];
}
else if (encoding == ZIP_INT_16B)
{
memcpy(&i16,p,sizeof(i16));
memrev16ifbe(&i16);
ret = i16;
}
else if (encoding == ZIP_INT_32B)
{
memcpy(&i32,p,sizeof(i32));
memrev32ifbe(&i32);
ret = i32;
}
else if (encoding == ZIP_INT_24B)
{
i32 = ;
memcpy(((uint8_t*)&i32)+,p,sizeof(i32)-sizeof(uint8_t));
memrev32ifbe(&i32);
ret = i32>>;
}
else if (encoding == ZIP_INT_64B)
{
memcpy(&i64,p,sizeof(i64));
memrev64ifbe(&i64);
ret = i64;
}
else if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX)
{
ret = (encoding & ZIP_INT_IMM_MASK)-;
}
else
{
assert(NULL);
} return ret;
} /* Return a struct with all information about an entry.
*
* 将 p 所指向的列表节点的信息全部保存到 zlentry 中,并返回该 zlentry 。
*
* T = O(1)
*/
static zlentry zipEntry(unsigned char *p)
{
zlentry e; // e.prevrawlensize 保存着编码前一个节点的长度所需的字节数
// e.prevrawlen 保存着前一个节点的长度
// T = O(1)
ZIP_DECODE_PREVLEN(p, e.prevrawlensize, e.prevrawlen); // p + e.prevrawlensize 将指针移动到列表节点本身
// e.encoding 保存着节点值的编码类型
// e.lensize 保存着编码节点值长度所需的字节数
// e.len 保存着节点值的长度
// T = O(1)
ZIP_DECODE_LENGTH(p + e.prevrawlensize, e.encoding, e.lensize, e.len); // 计算头结点的字节数
e.headersize = e.prevrawlensize + e.lensize; // 记录指针
e.p = p; return e;
} /* Create a new empty ziplist.
*
* 创建并返回一个新的 ziplist
*
* T = O(1)
*/
unsigned char *ziplistNew(void)
{ // ZIPLIST_HEADER_SIZE 是 ziplist 表头的大小
// 1 字节是表末端 ZIP_END 的大小
unsigned int bytes = ZIPLIST_HEADER_SIZE+; // 为表头和表末端分配空间
unsigned char *zl = zmalloc(bytes); // 初始化表属性
ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);
ZIPLIST_LENGTH(zl) = ; // 设置表末端
zl[bytes-] = ZIP_END; return zl;
} /* Resize the ziplist.
*
* 调整 ziplist 的大小为 len 字节。
*
* 当 ziplist 原有的大小小于 len 时,扩展 ziplist 不会改变 ziplist 原有的元素。
*
* T = O(N)
*/
static unsigned char *ziplistResize(unsigned char *zl, unsigned int len)
{ // 用 zrealloc ,扩展时不改变现有元素
zl = zrealloc(zl,len); // 更新 bytes 属性
ZIPLIST_BYTES(zl) = intrev32ifbe(len); // 重新设置表末端
zl[len-] = ZIP_END; return zl;
} /*
* 连锁更新
*
* 当将一个新节点添加到某个节点之前的时候,
* 如果原节点的 header 空间不足以保存新节点的长度,
* 那么就需要对原节点的 header 空间进行扩展(从 1 字节扩展到 5 字节)。
*
* 但是,当对原节点进行扩展之后,原节点的下一个节点的 prevlen 可能出现空间不足,
* 这种情况在多个连续节点的长度都接近 ZIP_BIGLEN 时可能发生。
*
* 这个函数就用于检查并修复后续节点的空间问题。
*
* 反过来说,
* 因为节点的长度变小而引起的连续缩小也是可能出现的,
* 不过,为了避免扩展-缩小-扩展-缩小这样的情况反复出现(flapping,抖动),
* 我们不处理这种情况,而是任由 prevlen 比所需的长度更长。
*
* 注意,程序的检查是针对 p 的后续节点,而不是 p 所指向的节点。
* 因为节点 p 在传入之前已经完成了所需的空间扩展工作。
*
* T = O(N^2)
*/
static 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; // T = O(N^2)
while (p[] != ZIP_END)
{ // 将 p 所指向的节点的信息保存到 cur 结构中
cur = zipEntry(p);
// 当前节点的长度
rawlen = cur.headersize + cur.len;
// 计算编码当前节点的长度所需的字节数
// T = O(1)
rawlensize = zipPrevEncodeLength(NULL,rawlen); /* Abort if there is no next entry. */
// 如果已经没有后续空间需要更新了,跳出
if (p[rawlen] == ZIP_END) break; // 取出后续节点的信息,保存到 next 结构中
// T = O(1)
next = zipEntry(p+rawlen); /* 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". */
// 执行到这里,表示 next 空间的大小不足以编码 cur 的长度
// 所以程序需要对 next 节点的(header 部分)空间进行扩展 // 记录 p 的偏移量
offset = p-zl;
// 计算需要增加的节点数量
extra = rawlensize-next.prevrawlensize;
// 扩展 zl 的大小
// T = O(N)
zl = ziplistResize(zl,curlen+extra);
// 还原指针 p
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. */
// 当 next 节点不是表尾节点时,更新列表到表尾节点的偏移量
//
// 不用更新的情况(next 为表尾节点):
//
// | | next | ==> | | new next |
// ^ ^
// | |
// tail tail
//
// 需要更新的情况(next 不是表尾节点):
//
// | next | | ==> | new next | |
// ^ ^
// | |
// old tail old tail
//
// 更新之后:
//
// | new next | |
// ^
// |
// new tail
// T = O(1)
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. */
// 向后移动 cur 节点之后的数据,为 cur 的新 header 腾出空间
//
// 示例:
//
// | header | value | ==> | header | | value | ==> | header | value |
// |<-->|
// 为新 header 腾出的空间
// T = O(N)
memmove(np+rawlensize,
np+next.prevrawlensize,
curlen-noffset-next.prevrawlensize-);
// 将新的前一节点长度值编码进新的 next 节点的 header
// T = O(1)
zipPrevEncodeLength(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. */
// 执行到这里,说明 next 节点编码前置节点的 header 空间有 5 字节
// 而编码 rawlen 只需要 1 字节
// 但是程序不会对 next 进行缩小,
// 所以这里只将 rawlen 写入 5 字节的 header 中就算了。
// T = O(1)
zipPrevEncodeLengthForceLarge(p+rawlen,rawlen);
}
else
{
// 运行到这里,
// 说明 cur 节点的长度正好可以编码到 next 节点的 header 中
// T = O(1)
zipPrevEncodeLength(p+rawlen,rawlen);
} /* Stop here, as the raw length of "next" has not changed. */
break;
}
} return zl;
} /* Delete "num" entries, starting at "p". Returns pointer to the ziplist.
*
* 从位置 p 开始,连续删除 num 个节点。
*
* 函数的返回值为处理删除操作之后的 ziplist 。
*
* T = O(N^2)
*/
static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num)
{
unsigned int i, totlen, deleted = ;
size_t offset;
int nextdiff = ;
zlentry first, tail; // 计算被删除节点总共占用的内存字节数
// 以及被删除节点的总个数
// T = O(N)
first = zipEntry(p);
for (i = ; p[] != ZIP_END && i < num; i++)
{
p += zipRawEntryLength(p);
deleted++;
} // totlen 是所有被删除节点总共占用的内存字节数
totlen = p-first.p;
if (totlen > )
{
if (p[] != ZIP_END)
{ // 执行这里,表示被删除节点之后仍然有节点存在 /* Storing `prevrawlen` in this entry may increase or decrease the
* number of bytes required compare to the current `prevrawlen`.
* There always is room to store this, because it was previously
* stored by an entry that is now being deleted. */
// 因为位于被删除范围之后的第一个节点的 header 部分的大小
// 可能容纳不了新的前置节点,所以需要计算新旧前置节点之间的字节数差
// T = O(1)
nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);
// 如果有需要的话,将指针 p 后退 nextdiff 字节,为新 header 空出空间
p -= nextdiff;
// 将 first 的前置节点的长度编码至 p 中
// T = O(1)
zipPrevEncodeLength(p,first.prevrawlen); /* Update offset for tail */
// 更新到达表尾的偏移量
// T = O(1)
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))-totlen); /* 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 记录的字节数也计算到表尾偏移量中
// 这样才能让表尾偏移量正确对齐表尾节点
// T = O(1)
tail = zipEntry(p);
if (p[tail.headersize+tail.len] != ZIP_END)
{
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
} /* Move tail to the front of the ziplist */
// 从表尾向表头移动数据,覆盖被删除节点的数据
// T = O(N)
memmove(first.p,p,
intrev32ifbe(ZIPLIST_BYTES(zl))-(p-zl)-);
}
else
{ // 执行这里,表示被删除节点之后已经没有其他节点了 /* The entire tail was deleted. No need to move memory. */
// T = O(1)
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe((first.p-zl)-first.prevrawlen);
} /* Resize and update length */
// 缩小并更新 ziplist 的长度
offset = first.p-zl;
zl = ziplistResize(zl, intrev32ifbe(ZIPLIST_BYTES(zl))-totlen+nextdiff);
ZIPLIST_INCR_LENGTH(zl,-deleted);
p = zl+offset; /* When nextdiff != 0, the raw length of the next entry has changed, so
* we need to cascade the update throughout the ziplist */
// 如果 p 所指向的节点的大小已经变更,那么进行级联更新
// 检查 p 之后的所有节点是否符合 ziplist 的编码要求
// T = O(N^2)
if (nextdiff != )
zl = __ziplistCascadeUpdate(zl,p);
} return zl;
}
/* Insert item at "p". */
/*
* 根据指针 p 所指定的位置,将长度为 slen 的字符串 s 插入到 zl 中。
*
* 函数的返回值为完成插入操作之后的 ziplist
*
* T = O(N^2)
*/
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen)
{
// 记录当前 ziplist 的长度
size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, prevlen = ;
size_t offset;
int nextdiff = ;
unsigned char encoding = ;
long long value = ; /* initialized to avoid warning. Using a value
that is easy to see if for some reason
we use it uninitialized. */
zlentry entry, tail; /* Find out prevlen for the entry that is inserted. */
if (p[] != ZIP_END)
{
// 如果 p[0] 不指向列表末端,说明列表非空,并且 p 正指向列表的其中一个节点
// 那么取出 p 所指向节点的信息,并将它保存到 entry 结构中
// 然后用 prevlen 变量记录前置节点的长度
// (当插入新节点之后 p 所指向的节点就成了新节点的前置节点)
// T = O(1)
entry = zipEntry(p);
prevlen = entry.prevrawlen;
}
else
{
// 如果 p 指向表尾末端,那么程序需要检查列表是否为:
// 1)如果 ptail 也指向 ZIP_END ,那么列表为空;
// 2)如果列表不为空,那么 ptail 将指向列表的最后一个节点。
unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
if (ptail[] != ZIP_END)
{
// 表尾节点为新节点的前置节点 // 取出表尾节点的长度
// T = O(1)
prevlen = zipRawEntryLength(ptail);
}
} /* See if the entry can be encoded */
// 尝试看能否将输入字符串转换为整数,如果成功的话:
// 1)value 将保存转换后的整数值
// 2)encoding 则保存适用于 value 的编码方式
// 无论使用什么编码, reqlen 都保存节点值的长度
// T = O(N)
if (zipTryEncoding(s,slen,&value,&encoding))
{
/* 'encoding' is set to the appropriate integer encoding */
reqlen = zipIntSize(encoding);
}
else
{
/* 'encoding' is untouched, however zipEncodeLength 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. */
// 计算编码前置节点的长度所需的大小
// T = O(1)
reqlen += zipPrevEncodeLength(NULL,prevlen);
// 计算编码当前节点值所需的大小
// T = O(1)
reqlen += zipEncodeLength(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. */
// 只要新节点不是被添加到列表末端,
// 那么程序就需要检查看 p 所指向的节点(的 header)能否编码新节点的长度。
// nextdiff 保存了新旧编码之间的字节大小差,如果这个值大于 0
// 那么说明需要对 p 所指向的节点(的 header )进行扩展
// T = O(1)
nextdiff = (p[] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : ; /* Store offset because a realloc may change the address of zl. */
// 因为重分配空间可能会改变 zl 的地址
// 所以在分配之前,需要记录 zl 到 p 的偏移量,然后在分配之后依靠偏移量还原 p
offset = p-zl;
// curlen 是 ziplist 原来的长度
// reqlen 是整个新节点的长度
// nextdiff 是新节点的后继节点扩展 header 的长度(要么 0 字节,要么 4 个字节)
// T = O(N)
zl = ziplistResize(zl,curlen+reqlen+nextdiff);
p = zl+offset; /* Apply memory move when necessary and update tail offset. */
if (p[] != ZIP_END)
{
// 新元素之后还有节点,因为新元素的加入,需要对这些原有节点进行调整 /* Subtract one because of the ZIP_END bytes */
// 移动现有元素,为新元素的插入空间腾出位置
// T = O(N)
memmove(p+reqlen,p-nextdiff,curlen-offset-+nextdiff); /* Encode this entry's raw length in the next entry. */
// 将新节点的长度编码至后置节点
// p+reqlen 定位到后置节点
// reqlen 是新节点的长度
// T = O(1)
zipPrevEncodeLength(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 记录的字节数也计算到表尾偏移量中
// 这样才能让表尾偏移量正确对齐表尾节点
// T = O(1)
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_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 时,新节点的后继节点的(header 部分)长度已经被改变,
// 所以需要级联地更新后续的节点
if (nextdiff != )
{
offset = p-zl;
// T = O(N^2)
zl = __ziplistCascadeUpdate(zl,p+reqlen);
p = zl+offset;
} /* Write the entry */
// 一切搞定,将前置节点的长度写入新节点的 header
p += zipPrevEncodeLength(p,prevlen);
// 将节点值的长度写入新节点的 header
p += zipEncodeLength(p,encoding,slen);
// 写入节点值
if (ZIP_IS_STR(encoding))
{
// T = O(N)
memcpy(p,s,slen);
}
else
{
// T = O(1)
zipSaveInteger(p,value,encoding);
} // 更新列表的节点数量计数器
// T = O(1)
ZIPLIST_INCR_LENGTH(zl,); return zl;
} /*
* 将长度为 slen 的字符串 s 推入到 zl 中。
*
* where 参数的值决定了推入的方向:
* - 值为 ZIPLIST_HEAD 时,将新值推入到表头。
* - 否则,将新值推入到表末端。
*
* 函数的返回值为添加新值后的 ziplist 。
*
* T = O(N^2)
*/
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where)
{ // 根据 where 参数的值,决定将值推入到表头还是表尾
unsigned char *p;
p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl); // 返回添加新值后的 ziplist
// T = O(N^2)
return __ziplistInsert(zl,p,s,slen);
} /*
* 根据给定索引,遍历列表,并返回索引指定节点的指针。
*
* 如果索引为正,那么从表头向表尾遍历。
* 如果索引为负,那么从表尾向表头遍历。
* 正数索引从 0 开始,负数索引从 -1 开始。
*
* 如果索引超过列表的节点数量,或者列表为空,那么返回 NULL 。
*
* T = O(N)
*/
unsigned char *ziplistIndex(unsigned char *zl, int index)
{ unsigned char *p; zlentry entry; // 处理负数索引
if (index < )
{ // 将索引转换为正数
index = (-index)-; // 定位到表尾节点
p = ZIPLIST_ENTRY_TAIL(zl); // 如果列表不为空,那么。。。
if (p[] != ZIP_END)
{ // 从表尾向表头遍历
entry = zipEntry(p);
// T = O(N)
while (entry.prevrawlen > && index--)
{
// 前移指针
p -= entry.prevrawlen;
// T = O(1)
entry = zipEntry(p);
}
} // 处理正数索引
}
else
{ // 定位到表头节点
p = ZIPLIST_ENTRY_HEAD(zl); // T = O(N)
while (p[] != ZIP_END && index--)
{
// 后移指针
// T = O(1)
p += zipRawEntryLength(p);
}
} // 返回结果
return (p[] == ZIP_END || index > ) ? NULL : p;
} /*
* 返回 p 所指向节点的后置节点。
*
* 如果 p 为表末端,或者 p 已经是表尾节点,那么返回 NULL 。
*
* T = O(1)
*/
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p)
{
((void) zl); // p 已经指向列表末端
if (p[] == ZIP_END)
{
return NULL;
} // 指向后一节点
p += zipRawEntryLength(p);
if (p[] == ZIP_END)
{
// p 已经是表尾节点,没有后置节点
return NULL;
} return p;
} /*
* 返回 p 所指向节点的前置节点。
*
* 如果 p 所指向为空列表,或者 p 已经指向表头节点,那么返回 NULL 。
*
* T = O(1)
*/
unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p)
{
zlentry entry; // 如果 p 指向列表末端(列表为空,或者刚开始从表尾向表头迭代)
// 那么尝试取出列表尾端节点
if (p[] == ZIP_END)
{
p = ZIPLIST_ENTRY_TAIL(zl);
// 尾端节点也指向列表末端,那么列表为空
return (p[] == ZIP_END) ? NULL : p; // 如果 p 指向列表头,那么说明迭代已经完成
}
else if (p == ZIPLIST_ENTRY_HEAD(zl))
{
return NULL; // 既不是表头也不是表尾,从表尾向表头移动指针
}
else
{
// 计算前一个节点的节点数
entry = zipEntry(p);
assert(entry.prevrawlen > );
// 移动指针,指向前一个节点
return p-entry.prevrawlen;
}
} /*
* 取出 p 所指向节点的值:
*
* - 如果节点保存的是字符串,那么将字符串值指针保存到 *sstr 中,字符串长度保存到 *slen
*
* - 如果节点保存的是整数,那么将整数保存到 *sval
*
* 程序可以通过检查 *sstr 是否为 NULL 来检查值是字符串还是整数。
*
* 提取值成功返回 1 ,
* 如果 p 为空,或者 p 指向的是列表末端,那么返回 0 ,提取值失败。
*
* T = O(1)
*/
unsigned int ziplistGet(unsigned char *p, unsigned char **sstr, unsigned int *slen, long long *sval)
{ zlentry entry;
if (p == NULL || p[] == ZIP_END) return ;
if (sstr) *sstr = NULL; // 取出 p 所指向的节点的各项信息,并保存到结构 entry 中
// T = O(1)
entry = zipEntry(p); // 节点的值为字符串,将字符串长度保存到 *slen ,字符串保存到 *sstr
// T = O(1)
if (ZIP_IS_STR(entry.encoding))
{
if (sstr)
{
*slen = entry.len;
*sstr = p+entry.headersize;
} // 节点的值为整数,解码值,并将值保存到 *sval
// T = O(1)
}
else
{
if (sval)
{
*sval = zipLoadInteger(p+entry.headersize,entry.encoding);
}
} return ;
} /* Insert an entry at "p".
*
* 将包含给定值 s 的新节点插入到给定的位置 p 中。
*
* 如果 p 指向一个节点,那么新节点将放在原有节点的前面。
*
* T = O(N^2)
*/
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen)
{
return __ziplistInsert(zl,p,s,slen);
} /*
* 从 zl 中删除 *p 所指向的节点,
* 并且原地更新 *p 所指向的位置,使得可以在迭代列表的过程中对节点进行删除。
*
* T = O(N^2)
*/
unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p)
{ // 因为 __ziplistDelete 时会对 zl 进行内存重分配
// 而内存充分配可能会改变 zl 的内存地址
// 所以这里需要记录到达 *p 的偏移量
// 这样在删除节点之后就可以通过偏移量来将 *p 还原到正确的位置
size_t offset = *p-zl;
zl = __ziplistDelete(zl,*p,); *p = zl+offset; return zl;
} /*
* 从 index 索引指定的节点开始,连续地从 zl 中删除 num 个节点。
*
* T = O(N^2)
*/
unsigned char *ziplistDeleteRange(unsigned char *zl, unsigned int index, unsigned int num)
{ // 根据索引定位到节点
// T = O(N)
unsigned char *p = ziplistIndex(zl,index); // 连续删除 num 个节点
// T = O(N^2)
return (p == NULL) ? zl : __ziplistDelete(zl,p,num);
} /*
* 将 p 所指向的节点的值和 sstr 进行对比。
*
* 如果节点值和 sstr 的值相等,返回 1 ,不相等则返回 0 。
*
* T = O(N)
*/
unsigned int ziplistCompare(unsigned char *p, unsigned char *sstr, unsigned int slen)
{
zlentry entry;
unsigned char sencoding;
long long zval, sval;
if (p[] == ZIP_END) return ; // 取出节点
entry = zipEntry(p);
if (ZIP_IS_STR(entry.encoding))
{ // 节点值为字符串,进行字符串对比 /* Raw compare */
if (entry.len == slen)
{
// T = O(N)
return memcmp(p+entry.headersize,sstr,slen) == ;
}
else
{
return ;
}
}
else
{ // 节点值为整数,进行整数对比 if (zipTryEncoding(sstr,slen,&sval,&sencoding))
{
// T = O(1)
zval = zipLoadInteger(p+entry.headersize,entry.encoding);
return zval == sval;
}
} return ;
} /*
* 寻找节点值和 vstr 相等的列表节点,并返回该节点的指针。
*
*
* 每次比对之前都跳过 skip 个节点。
*
*
* 如果找不到相应的节点,则返回 NULL 。
*
* T = O(N^2)
*/
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip)
{
int skipcnt = ;
unsigned char vencoding = ;
long long vll = ; // 只要未到达列表末端,就一直迭代
// T = O(N^2)
while (p[] != ZIP_END)
{
unsigned int prevlensize, encoding, lensize, len;
unsigned char *q; ZIP_DECODE_PREVLENSIZE(p, prevlensize);
ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);
q = p + prevlensize + lensize; if (skipcnt == )
{ /* Compare current entry with specified entry */
// 对比字符串值
// T = O(N)
if (ZIP_IS_STR(encoding))
{
if (len == vlen && memcmp(q, vstr, vlen) == )
{
return p;
}
}
else
{
// 因为传入值有可能被编码了,
// 所以当第一次进行值对比时,程序会对传入值进行解码
// 这个解码操作只会进行一次
if (vencoding == )
{
if (!zipTryEncoding(vstr, vlen, &vll, &vencoding))
{
vencoding = UCHAR_MAX;
}
/* Must be non-zero by now */
assert(vencoding);
} // 对比整数值
if (vencoding != UCHAR_MAX)
{
// T = O(1)
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;
} /* Return length of ziplist.
*
* 返回 ziplist 中的节点个数
*
* T = O(N)
*/
unsigned int ziplistLen(unsigned char *zl)
{ unsigned int len = ; // 节点数小于 UINT16_MAX
// T = O(1)
if (intrev16ifbe(ZIPLIST_LENGTH(zl)) < UINT16_MAX)
{
len = intrev16ifbe(ZIPLIST_LENGTH(zl)); // 节点数大于 UINT16_MAX 时,需要遍历整个列表才能计算出节点数
// T = O(N)
}
else
{
unsigned char *p = zl+ZIPLIST_HEADER_SIZE;
while (*p != ZIP_END)
{
p += zipRawEntryLength(p);
len++;
} /* Re-store length if small enough */
if (len < UINT16_MAX) ZIPLIST_LENGTH(zl) = intrev16ifbe(len);
} return len;
} /* Return ziplist blob size in bytes.
*
* 返回整个 ziplist 占用的内存字节数
*
* T = O(1)
*/
size_t ziplistBlobLen(unsigned char *zl)
{
return intrev32ifbe(ZIPLIST_BYTES(zl));
} void ziplistRepr(unsigned char *zl)
{
unsigned char *p;
int index = ;
zlentry entry; printf(
"{total bytes %d} "
"{length %u}\n"
"{tail offset %u}\n",
intrev32ifbe(ZIPLIST_BYTES(zl)),
intrev16ifbe(ZIPLIST_LENGTH(zl)),
intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)));
p = ZIPLIST_ENTRY_HEAD(zl);
while(*p != ZIP_END)
{
entry = zipEntry(p);
printf(
"{"
"addr 0x%08lx, "
"index %2d, "
"offset %5ld, "
"rl: %5u, "
"hs %2u, "
"pl: %5u, "
"pls: %2u, "
"payload %5u"
"} ",
(long unsigned)p,
index,
(unsigned long) (p-zl),
entry.headersize+entry.len,
entry.headersize,
entry.prevrawlen,
entry.prevrawlensize,
entry.len);
p += entry.headersize;
if (ZIP_IS_STR(entry.encoding))
{
if (entry.len > )
{
if (fwrite(p,,,stdout) == ) perror("fwrite");
printf("...");
}
else
{
if (entry.len &&
fwrite(p,entry.len,,stdout) == ) perror("fwrite");
}
}
else
{
printf("%lld", (long long) zipLoadInteger(p,entry.encoding));
}
printf("\n");
p += entry.len;
index++;
}
printf("{end}\n\n");
}