一.压缩列表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[0]); \ if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; \ } while(0) /* * 返回保存 encoding 编码的值所需的字节数量 * * T = O(1) */ static unsigned int zipIntSize(unsigned char encoding) { switch(encoding) { case ZIP_INT_8B: return 1; case ZIP_INT_16B: return 2; case ZIP_INT_24B: return 3; case ZIP_INT_32B: return 4; case ZIP_INT_64B: return 8; default: return 0; /* 4 bit immediate */ } assert(NULL); return 0; } /* * 编码节点长度值 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 = 1, buf[5]; // 编码字符串 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[0] = ZIP_STR_06B | rawlen; } else if (rawlen <= 0x3fff) { len += 1; if (!p) return len; buf[0] = ZIP_STR_14B | ((rawlen >> 8) & 0x3f); buf[1] = rawlen & 0xff; } else { len += 4; if (!p) return len; buf[0] = ZIP_STR_32B; buf[1] = (rawlen >> 24) & 0xff; buf[2] = (rawlen >> 16) & 0xff; buf[3] = (rawlen >> 8) & 0xff; buf[4] = rawlen & 0xff; } // 编码整数 } else { /* Implies integer encoding, so length is always 1. */ if (!p) return len; buf[0] = 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) = 1; \ (len) = (ptr)[0] & 0x3f; \ } else if ((encoding) == ZIP_STR_14B) { \ (lensize) = 2; \ (len) = (((ptr)[0] & 0x3f) << 8) | (ptr)[1]; \ } else if (encoding == ZIP_STR_32B) { \ (lensize) = 5; \ (len) = ((ptr)[1] << 24) | \ ((ptr)[2] << 16) | \ ((ptr)[3] << 8) | \ ((ptr)[4]); \ } else { \ assert(NULL); \ } \ \ /* 整数编码 */ \ } else { \ (lensize) = 1; \ (len) = zipIntSize(encoding); \ } \ } while(0); /* * 对前置节点的长度 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) ? 1 : sizeof(len)+1; // 写入并返回编码 len 所需的字节数量 } else { // 1 字节 if (len < ZIP_BIGLEN) { p[0] = len; return 1; // 5 字节 } else { // 添加 5 字节长度标识 p[0] = ZIP_BIGLEN; // 写入编码 memcpy(p+1,&len,sizeof(len)); // 如果有必要的话,进行大小端转换 memrev32ifbe(p+1); // 返回编码长度 return 1+sizeof(len); } } } /* * * 将原本只需要 1 个字节来保存的前置节点长度 len 编码至一个 5 字节长的 header 中。 * * T = O(1) */ static void zipPrevEncodeLengthForceLarge(unsigned char *p, unsigned int len) { if (p == NULL) return; // 设置 5 字节长度标识 p[0] = ZIP_BIGLEN; // 写入 len memcpy(p+1,&len,sizeof(len)); memrev32ifbe(p+1); } /* * 解码 ptr 指针, * 取出编码前置节点长度所需的字节数,并将它保存到 prevlensize 变量中。 * * T = O(1) */ #define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do { \ if ((ptr)[0] < ZIP_BIGLEN) { \ (prevlensize) = 1; \ } else { \ (prevlensize) = 5; \ } \ } while(0); /* * 解码 ptr 指针, * 取出编码前置节点长度所需的字节数, * 并将这个字节数保存到 prevlensize 中。 * * 然后根据 prevlensize ,从 ptr 中取出前置节点的长度值, * 并将这个长度值保存到 prevlen 变量中。 * * T = O(1) */ #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((prevlensize)) == 4); \ memcpy(&(prevlen), ((char*)(ptr)) + 1, 4); \ memrev32ifbe(&prevlen); \ } \ } while(0); /* * 计算编码新的前置节点长度 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 >= 32 || entrylen == 0) return 0; // 尝试转换 // 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 >= 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; } /* 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)[0] = (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<<8; memrev32ifbe(&i32); memcpy(p,((uint8_t*)&i32)+1,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 = 0; if (encoding == ZIP_INT_8B) { ret = ((int8_t*)p)[0]; } 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 = 0; memcpy(((uint8_t*)&i32)+1,p,sizeof(i32)-sizeof(uint8_t)); memrev32ifbe(&i32); ret = i32>>8; } 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)-1; } 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+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; } /* 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-1] = 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[0] != 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-1); // 将新的前一节点长度值编码进新的 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 = 0; size_t offset; int nextdiff = 0; zlentry first, tail; // 计算被删除节点总共占用的内存字节数 // 以及被删除节点的总个数 // T = O(N) first = zipEntry(p); for (i = 0; p[0] != ZIP_END && i < num; i++) { p += zipRawEntryLength(p); deleted++; } // totlen 是所有被删除节点总共占用的内存字节数 totlen = p-first.p; if (totlen > 0) { if (p[0] != 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)-1); } 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 != 0) 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 = 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 entry, tail; /* Find out prevlen for the entry that is inserted. */ if (p[0] != 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[0] != 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[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0; /* 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[0] != ZIP_END) { // 新元素之后还有节点,因为新元素的加入,需要对这些原有节点进行调整 /* Subtract one because of the ZIP_END bytes */ // 移动现有元素,为新元素的插入空间腾出位置 // T = O(N) memmove(p+reqlen,p-nextdiff,curlen-offset-1+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 != 0) { 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,1); 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 < 0) { // 将索引转换为正数 index = (-index)-1; // 定位到表尾节点 p = ZIPLIST_ENTRY_TAIL(zl); // 如果列表不为空,那么。。。 if (p[0] != ZIP_END) { // 从表尾向表头遍历 entry = zipEntry(p); // T = O(N) while (entry.prevrawlen > 0 && index--) { // 前移指针 p -= entry.prevrawlen; // T = O(1) entry = zipEntry(p); } } // 处理正数索引 } else { // 定位到表头节点 p = ZIPLIST_ENTRY_HEAD(zl); // T = O(N) while (p[0] != ZIP_END && index--) { // 后移指针 // T = O(1) p += zipRawEntryLength(p); } } // 返回结果 return (p[0] == ZIP_END || index > 0) ? NULL : p; } /* * 返回 p 所指向节点的后置节点。 * * 如果 p 为表末端,或者 p 已经是表尾节点,那么返回 NULL 。 * * T = O(1) */ unsigned char *ziplistNext(unsigned char *zl, unsigned char *p) { ((void) zl); // p 已经指向列表末端 if (p[0] == ZIP_END) { return NULL; } // 指向后一节点 p += zipRawEntryLength(p); if (p[0] == 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[0] == ZIP_END) { p = ZIPLIST_ENTRY_TAIL(zl); // 尾端节点也指向列表末端,那么列表为空 return (p[0] == ZIP_END) ? NULL : p; // 如果 p 指向列表头,那么说明迭代已经完成 } else if (p == ZIPLIST_ENTRY_HEAD(zl)) { return NULL; // 既不是表头也不是表尾,从表尾向表头移动指针 } else { // 计算前一个节点的节点数 entry = zipEntry(p); assert(entry.prevrawlen > 0); // 移动指针,指向前一个节点 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[0] == ZIP_END) return 0; 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 1; } /* 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,1); *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[0] == ZIP_END) return 0; // 取出节点 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) == 0; } else { return 0; } } else { // 节点值为整数,进行整数对比 if (zipTryEncoding(sstr,slen,&sval,&sencoding)) { // T = O(1) zval = zipLoadInteger(p+entry.headersize,entry.encoding); return zval == sval; } } return 0; } /* * 寻找节点值和 vstr 相等的列表节点,并返回该节点的指针。 * * * 每次比对之前都跳过 skip 个节点。 * * * 如果找不到相应的节点,则返回 NULL 。 * * T = O(N^2) */ 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; // 只要未到达列表末端,就一直迭代 // T = O(N^2) while (p[0] != 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 == 0) { /* Compare current entry with specified entry */ // 对比字符串值 // T = O(N) if (ZIP_IS_STR(encoding)) { if (len == vlen && memcmp(q, vstr, vlen) == 0) { return p; } } else { // 因为传入值有可能被编码了, // 所以当第一次进行值对比时,程序会对传入值进行解码 // 这个解码操作只会进行一次 if (vencoding == 0) { 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 = 0; // 节点数小于 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 = 0; 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 > 40) { if (fwrite(p,40,1,stdout) == 0) perror("fwrite"); printf("..."); } else { if (entry.len && fwrite(p,entry.len,1,stdout) == 0) perror("fwrite"); } } else { printf("%lld", (long long) zipLoadInteger(p,entry.encoding)); } printf("\n"); p += entry.len; index++; } printf("{end}\n\n"); }