一.壓縮列表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"); }