Note:本文实际绑定的版本是branch5.0(2018-7-25)。
数据结构
SDS 简单动态字符串
基本特点
- 涉及字符串的存储基本都基于SDS,例如set nickname Luffy,就创建了nicknmae和Luffy2个SDS
- 不止用于字符串,还用于缓冲区:AOF缓冲区、客户端状态中的输入缓冲区
- 减少修改字符串长度时所需的内存重分配次数
- 杜绝缓冲区溢出问题
- 二进制安全
- 兼容部分C字符串函数
源码位置
- sds.h
- sds.c
结构定义
typedef char *sds;
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
- sds被定义为5种,根据sds的len属性的bit数量划分:5、8、16、32、64bits,5bits的结构没有被使用。
- 紧凑对齐:attribute ((packed)) 告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。
- char buf[]很有意思,这样子声明buf,是不会增大sizeof(sdshdr#T)的,因为编译器不知道buf的长度,默认0,而如果声明成指针,就会占4或8字节。
- 综上,5个sds结构的size分别为1、3、5、9、17
- typedef char *sds,指向的是buf字段,如果要访问sdshdr的len、alloc、flags,是用宏SDS_HDR来定位sdshdr的首字节。
- sds依然遵守C语言用'\0'(null terminator)结尾的习惯,使得sds可以使用C字符串函数库。
- len相当于strlen(buf) - 1,记录实际使用了buf的多少字节
- alloc记录buf的容量,不含'\0'
- '\0'由sds自动处理,用户不会感知到'\0';len、alloc也是
- 因为有len字段,获取sds长度时间复杂度为O(1)
- len、alloc字段能防止缓冲区溢出
- alloc - len = avail, avail有效地降低了频繁内存重分配的开销。这种策略叫空间预分配、惰性空间释放。
接口说明
sds.h直接定义的简单接口:
- size_t sdslen(const sds s):获得s的len字段
- size_t sdsavail(const sds s):返回这个s的空闲空间字节长度
- sdssetlen(sds s, size_t newlen):直接设置s的len字段(不验证上限的)
- sdsinclen(sds s, size_t inc):s的len增加inc(不验证上限的)
- size_t sdsalloc(const sds s):获得s的alloc字段(并不是分配一个sds)
- sdssetalloc(sds s, size_t newlen):直接设置s的alloc字段
在sds.c定义的简单接口:
- int sdsHdrSize(char type):根据SDS_TYPE_xx,返回对应的sdshdr结构的sizeof
- char sdsReqType(size_t string_size):根据给定大小,返回对应的sdshdr的类型SDS_TYPE_xx,策略是从小到大匹配
- size_t sdsAllocSize(sds s):等于sdsalloc(s) + 头部长度 + 1,即sds总共占了多少内存
- void *sdsAllocPtr(sds s):即得到s的sdshdr头部指针
- sds sdsempty(void):实际调用sdsnewlen("",0);
- sds sdsnew(const char *init):把C字符串转成sds
- sds sdsdup(const sds s):克隆一个sds
核心接口:
- sds sdsnewlen(const void *init, size_t initlen):创建一个新的sds对象,内容为init,len字段为initlen
- void sdsfree(sds s):销毁一个sds
- void sdsupdatelen(sds s):更新s的len为strlen的结果,如果sds某个字节被置0,就会导致strlen长度发生变化。
- void sdsclear(sds s):清空sds,但不释放sds内存
- sds sdsMakeRoomFor(sds s, size_t addlen):增大sds的alloc,但不影响已存内容和len字段,会返回新的sds(地址发生改变)
- sds sdsRemoveFreeSpace(sds s):缩小sds的alloc,使得没有avail空间(100%利用率),会返回新的sds
- void sdsIncrLen(sds s, ssize_t incr):和sdsinclen类似,区别在于加了assert防止超过上限,以及会把newlen字节置0
- sds sdsgrowzero(sds s, size_t len):oldlen增大到len,len - oldlen这段空间会自动置0
- sds sdscatlen(sds s, const void *t, size_t len):即concat操作
- sds sdscat(sds s, const char *t):用strlen(t)去调用sdscatlen
- sds sdscatsds(sds s, const sds t):concat2个sds
- sds sdscpylen(sds s, const char *t, size_t len):把t复制进sds
- sds sdscpy(sds s, const char *t):用strlen(t)去调用sdscpylen
其他接口:
- int sdsll2str(char *s, long long value):long long转成字符串并放进s缓冲区
- int sdsull2str(char *s, unsigned long long v):同上
- sds sdsfromlonglong(long long value) :long long转成sds
- sds sdscatvprintf(sds s, const char *fmt, va_list ap) :打印相关
- sds sdscatprintf(sds s, const char *fmt, ...):打印相关
- sds sdscatfmt(sds s, char const *fmt, ...):格式化相关
- sds sdstrim(sds s, const char *cset) :trim掉头尾连续字符串,用cset识别
- void sdsrange(sds s, ssize_t start, ssize_t end):返回s的子串
- void sdstolower(sds s):转小写
- void sdstoupper(sds s):转大写
- int sdscmp(const sds s1, const sds s2):比较两个sds
- sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count):用指定的sep字符串切割sds
- void sdsfreesplitres(sds *tokens, int count):sdssplitlen、sdssplitargs后要调用这个释放数组
- sds sdscatrepr(sds s, const char *p, size_t len) :concat p串,p会被转成escaped的串
- int is_hex_digit(char c):判断是不是hex字符
- int hex_digit_to_int(char c):hex字符转int
- sds *sdssplitargs(const char *line, int *argc):把用空格间隔的参数列表转成sds数组
- sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) :sdsmapchars(mystring, "ho", "01", 2),"hello" -> "0ell1"
- sds sdsjoin(char **argv, int argc, char *sep):join一组C字符串
- sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen):join一组SDS对象
adlist 泛型双向链表
基本特点
- 增加一个len字段用来记录链表长度,使得获取链表长度O(1)
泛型原理:
- value字段用void*类型,使得可以存任意类型的对象
- list结构里存了3个函数指针:dup、free、match,使得不同类型的list可以自定义节点的复制、销毁、匹配函数
源码位置
- adlist.h
- adlist.c
结构定义
/* Node, List, and Iterator are the only data structures used currently. */
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
typedef struct listIter {
listNode *next;
int direction;
} listIter;
typedef struct list {
listNode *head;
listNode *tail;
void *(*dup)(void *ptr);
void (*free)(void *ptr);
int (*match)(void *ptr, void *key);
unsigned long len;
} list;
接口说明
- list *listCreate(void); 创建新的list对象
- void listEmpty(list *list); 清空list里的所有元素
- void listRelease(list *list); 调用了listEmpty(list)、zfree(list)
- list *listAddNodeHead(list *list, void *value); 创建一个存value的node并插在链表头
- list *listAddNodeTail(list *list, void *value);创建一个存value的node并插在链表尾
- list *listInsertNode(list *list, listNode *old_node, void *value, int after); 创建一个存value的node并插在old_node的前或后
- void listDelNode(list *list, listNode *node); 删node
- listIter *listGetIterator(list *list, int direction); 创建迭代器
- listNode *listNext(listIter *iter); 根据迭代方向步进1
- void listReleaseIterator(listIter *iter);删迭代器
- void listRewind(list *list, listIter *li); 重置迭代器到链表头
- void listRewindTail(list *list, listIter *li);重置迭代器到链表尾
- list *listDup(list *orig); 复制整个链表,会调用dup接口
- listNode *listSearchKey(list *list, void *key); 找出包含key的节点,会调用match接口
- listNode *listIndex(list *list, long index); 模拟数组下标操作
- void listRotate(list *list); 把tail移到head
- void listJoin(list *l, list *o); 把整个链表o插到l末尾,并empty掉o
dict 泛型字典
基本特点
- 字典基于散列表
- 每个dict有2个dictht,用来实现rehashing,即扩容操作
- rehashing是渐进式的,不是在瞬间做完
- 键冲突的解决方案是chaining,于是每个dictEntry都有一个next指针,用来构成同哈希键链表
- 因为只有next指针,所以chain是单向的,为了加速插入性能,后加入的kv,会插到链表头而不是链表尾
- rehash和chaining的特点,使得rehashing时,新的表项都插入到dt[1],而dt用chaing没有容量限制,于是rehashing过程总能完成
源码位置
- dict.h
- dict.c
- siphash.c
结构定义
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
// dictType实现泛型,在server.c里有各种dictType的定义
typedef struct dictType {
uint64_t (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key);
void *(*valDup)(void *privdata, const void *obj);
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
} dictType;
typedef struct dictht {
dictEntry **table; // 表项
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
unsigned long iterators; /* number of iterators currently running */
} dict;
typedef struct dictIterator {
dict *d;
long index;
int table, safe;
dictEntry *entry, *nextEntry;
/* unsafe iterator fingerprint for misuse detection. */
long long fingerprint;
} dictIterator;
typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
typedef void (dictScanBucketFunction)(void *privdata, dictEntry **bucketref);
/* This is the initial size of every hash table */
#define DICT_HT_INITIAL_SIZE 4
关键逻辑
rehashing
扩容和收缩是通过dictExpand。
dictExpand让一个dict扩大或缩小到size(实际是_dictNextPower(size)),前提条件是used数要小于size,不然就意味着新的size不足以容纳现有的元素。
dictExpand最终会设置d->ht[1] = n 和 d->rehashidx = 0; 启动rehashing程序。
之后,dict.c里有大量地方用dictIsRehashing宏,走不同的逻辑,处理rehashing。
rehashing的迭代接口是dictRehash,参数n表示此次要迭代多少步。
调用dictRehash的地方:
_dictRehashStep里调用dictRehash(d,1) dictRehashMilliseconds里调用dictRehash(d,100),超时了就结束迭代。dictRehashMilliseconds只在server.c里调用。
调用dictExpand的地方:
- dictResize:把dict缩小到used大小,使得空间利用率尽量靠近1。dictResize并不在dict.c调用,所以是由上层策略决定的,实际上是根据htNeedsResize,即ratio小于0.1时,就会开始收缩。
- _dictKeyIndex:每次调用都会执行_dictExpandIfNeeded,如果used已经大于size且两者之比ratio大于5,就调用dictExpand开始扩容(说明有很多chain了)
每次dictExpand,都会创建一个dictht,并分配空间:
n.table = zcalloc(realsize*sizeof(dictEntry*)); // realsize个桶
dictRehash的rehashing完成时:
/* Check if we already rehashed the whole table... */
if (d->ht[0].used == 0) {
zfree(d->ht[0].table);
d->ht[0] = d->ht[1];
_dictReset(&d->ht[1]);
d->rehashidx = -1;
return 0;
}
缺省哈希函数:siphash
缺省哈希函数是siphash,以前是murmurhash2。
跟踪dictType的hashFunction调用,最终会找到dict.c里的:
uint64_t siphash(const uint8_t *in, const size_t inlen, const uint8_t *k);
uint64_t siphash_nocase(const uint8_t *in, const size_t inlen, const uint8_t *k);
这2个函数是在 siphash.c里定义的,siphash.c有且只有这2个函数。
k是dict_hash_function_seed,一个dict.c里的静态全局变量,在服务启动时初始化。
zskiplist 跳跃表
基本特点
- 用来存放有序集合(zset)
- 集合元素不能重复,但分值score可以重复
- 最高level是64,旧版本是32(ZSKIPLIST_MAXLEVEL)
源码位置
- server.h
- t_zset.c
结构定义
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned long span;
} level[];
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
表头节点是特殊的,不存数据(ele、score无用)。
资料
精简的skiplist实现:
https://github.com/begeekmyfriend/skiplist
intset 整数集合
基本特点
- 用紧凑的数组结构来存放集合元素,缓存命中率高
- 插入、删除性能不会太好,会触发realloc,如果是中间元素,还会触发大段memcpy
- 查找性能极好,因为数据有序,是基于数组的迭代式的二分查找
- 元素不能重复,省却了大量插入开销
- 升级逻辑使得存放int16、int32的开销要比int64小得多,节约内存
- 不支持降级
源码位置
- intset.h
- intset.c
结构定义
typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;
接口说明
内部接口:
- uint8_t _intsetValueEncoding(int64_t v) 根据v的值范围,返回INTSET_ENC_INT64/INTSET_ENC_INT32/INTSET_ENC_INT16
对外接口:
- intset *intsetNew(void); zmalloc一个intset,缺省编码是INTSET_ENC_INT16,contents为空
- intset *intsetAdd(intset *is, int64_t value, uint8_t *success); 看下文。
- intset *intsetRemove(intset *is, int64_t value, int *success); 移除一个元素,会引起该元素之后的所有元素的整体移动;不会降级
- uint8_t intsetFind(intset *is, int64_t value); 二分查找一个元素
- int64_t intsetRandom(intset *is); 随机返回一个元素,基于random()
- uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value); 返回指定元素
- uint32_t intsetLen(const intset *is); 返回is->length
- size_t intsetBlobLen(intset *is); 返回is总共占内存多少字节
关键逻辑
intsetAdd(intset *is, int64_t value, uint8_t *success);
先获取value的编码,然后对比is的编码,如果value编码大于is的编码,那么转而执行intsetUpgradeAndAdd(is,value),没有传入success是因为value肯定不在集合中,毕竟越界了;如果value编码小于等于is的编码,就要检查value是不是已经在集合中(调用intsetSearch(is,value,&pos)),如果在,那么中止,如果不在,pos会设成value可以插入的位置,intsetResize这个is到length + 1,并判断pos是不是小于newlength - 1,是的话意味着插入位置后面还有元素,那么要把pos后的元素全体向右挪1(intsetMoveTail)。
最后,调用_intsetSet(is,pos,value),把value放进pos位置,并更新is->length。
intset *intsetUpgradeAndAdd(intset *is, int64_t value)
先intsetResize扩容到length+1,然后从后到前把旧数据刷到新的位置,防止覆盖;刷完后,必然剩一个位置,或者在最前或者在最后,根据value的正负,把value插到头或尾即可。
升级后就再不会降级的。
ifbe
在这个intset.c里经常看到这个后缀,含义是 if big endian(是否是大端字节序)。
ziplist 压缩列表
基本特点
- 通过在每个entry维护一个prevlen字段,使得逆向遍历链表变成可能(双向链表)
- 删除节点可能会引发连锁更新,因为prevlen不一定够空间存长度,这样会导致连锁性的内存重分配
- 压缩列表的压缩体现在对于小整数的存储是高度优化的,减少了大量空间浪费
源码位置
- ziplist.h
- ziplist.c
结构定义
typedef struct zlentry {
unsigned int prevrawlensize; // 上一节点头部长度
unsigned int prevrawlen; // 上一节点内容部分的长度
unsigned int lensize; // 当前节点头部长度
unsigned int len; // 当前节点内容部分的长度
unsigned int headersize; // 上一节点头部长度 + 当前节点头部长度
unsigned char encoding; // 编码
unsigned char *p; /* points to prev-entry-len field
} zlentry;
实际上zlentry不是直接用的,这相当于是个结构说明。
表头没有结构,而是用三个宏访问:
/* Return total bytes a ziplist is composed of. */
#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl)))
/* Return the offset of the last item inside the ziplist. */
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
/* Return the length of a ziplist, or UINT16_MAX if the length cannot be
* determined without scanning the whole ziplist. */
#define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
表头:
- zlbytes:表示这个ziplist占用内存多少字节
- zltail:表示这个ziplist头到最后一个entry的偏移,初始偏移量等于头部大小(10字节)
- zllen:表示这个ziplist中节点的数量
表头占4+4+2 = 10字节,表尾1字节(255)。
注释翻译
ziplist.c有一大段注释值得一看,翻译下。
/*
* ZIPLIST ENTRIES 列表项
* ===============
*
* Every entry in the ziplist is prefixed by metadata that contains two pieces
* of information. First, the length of the previous entry is stored to be
* able to traverse the list from back to front. Second, the entry encoding is
* provided. It represents the entry type, integer or string, and in the case
* of strings it also represents the length of the string payload.
* So a complete entry is stored like this:
* ziplist的每一entry都带了一个metadata前缀,metadata里有2个信息。一个是,上一entry
* 的长度,用于从后向前遍历列表;另一个是entry的编码encoding,编码记录了entry类型
* (整数/字符串),如果是字符串的话那么还记录了字符串的长度。
* 所有完整的entry的存储结构如下:
* <prevlen> <encoding> <entry-data>
*
* Sometimes the encoding represents the entry itself, like for small integers
* as we'll see later. In such a case the <entry-data> part is missing, and we
* could have just:
* 有时候编码已经表示了entry本身,例如小整数,这种情况下,entry-data是没有的:
* <prevlen> <encoding>
*
* The length of the previous entry, <prevlen>, is encoded in the following way:
* If this length is smaller than 254 bytes, it will only consume a single
* byte representing the length as an unsinged 8 bit integer. When the length
* is greater than or equal to 254, it will consume 5 bytes. The first byte is
* set to 254 (FE) to indicate a larger value is following. The remaining 4
* bytes take the length of the previous entry as value.
* prevlen被用如下方式编码:
* 如果长度小于254字节,用1个字节就足够表示了;当长度大于等于254,那么需要5字节,
* 第1个字节被设为254(0xFE),表示余下4字节才是真正存了长度信息的。
* (这里说明下,不是以255为分界的原因是,255是用来表达ziplist的结尾的,有特殊含义。)
* So practically an entry is encoded in the following way:
* 所以entry实际上是这样编码的:
*
* <prevlen from 0 to 253> <encoding> <entry>
*
* Or alternatively if the previous entry length is greater than 253 bytes
* the following encoding is used:
* 或者这样(上一entry长度大于等于254字节时):
*
* 0xFE <4 bytes unsigned little endian prevlen> <encoding> <entry>
*
* The encoding field of the entry depends on the content of the
* entry. When the entry is a string, the first 2 bits of the encoding first
* byte will hold the type of encoding used to store the length of the string,
* followed by the actual length of the string. When the entry is an integer
* the first 2 bits are both set to 1. The following 2 bits are used to specify
* what kind of integer will be stored after this header. An overview of the
* different types and encodings is as follows. The first byte is always enough
* to determine the kind of entry.
* encoding字段的最左2bits,保存了编码类型,00/01/10是字符串类型,11是整数类型。
* 如果是11,那么接下来的2bits,保存了整数子类型。
* encoding的总长度是动态的,取决于encoding首字节里的定义。
*
* |00pppppp| - 1 byte
* String value with length less than or equal to 63 bytes (6 bits).
* "pppppp" represents the unsigned 6 bit length.
* 00:字符串长度小于等于63字节(6bits能表示的范围),长度信息存在pppppp里。
* |01pppppp|qqqqqqqq| - 2 bytes
* String value with length less than or equal to 16383 bytes (14 bits).
* IMPORTANT: The 14 bit number is stored in big endian.
* 01:字符串长度小于等于16383字节(14bits能表示的范围)
* 注意,这个14bits数字是用大端字节序的
* |10000000|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes
* String value with length greater than or equal to 16384 bytes.
* Only the 4 bytes following the first byte represents the length
* up to 32^2-1. The 6 lower bits of the first byte are not used and
* are set to zero.
* IMPORTANT: The 32 bit number is stored in big endian.
* 10:字符串长度大于16384字节。
* 不过这次pppppp没有被使用,而是置0,另外加了4个字节来存长度
* 注意,4字节的长度是用大端字节序的
* |11 00 0000| - 3 bytes
* Integer encoded as int16_t (2 bytes).
* 双字节的整数
* |11 01 0000| - 5 bytes
* Integer encoded as int32_t (4 bytes).
* 4字节的整数
* |11 10 0000| - 9 bytes
* Integer encoded as int64_t (8 bytes).
* 8字节的整数
* |11 11 0000| - 4 bytes
* Integer encoded as 24 bit signed (3 bytes).
* 3字节的整数
* |11 11 1110| - 2 bytes
* Integer encoded as 8 bit signed (1 byte).
* 1字节的整数
* |11 11 xxxx| - (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.
* 后4bits如果不是0000/1110,那么就是表示[0,12]范围内的某一个数。
* 因为0000有特殊用途了,所以这4bits实际上是存了[1,13]范围的总共13个数。
* 为了能表示[0,12],需要做一个-1的操作。所以实际是存[0001, 1101]
* |11 11 1111| - End of ziplist special entry.
* 这个就不是什么整数了,而是ziplist的end标识
*
* Like for the ziplist header, all the integers are represented in little
* endian byte order, even when this code is compiled in big endian systems.
* 所有证书都用小端字节序,即使是在大端字节序系统里编译也是。
*
* EXAMPLES OF ACTUAL ZIPLISTS
* ===========================
* 实例:
*
* [0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff]
* | | | | | |
* zlbytes zltail entries "2" "5" end
* (这个图是前3个[]用大端字节序表示的,[]里的最左边字节才是最低位的)
* zlbytes是0x0000000f,表示这个ziplist占内存15字节。
* zltail是0x0000000c,表示从ziplist起始位置到最后1个entry(02 f6),距离是12字节。
* zllen是0x0002,表示总共存了2个元素。
* 接下里的就是元素列表了,[00 f3],00是prevlen,因为是首个元素,所以为0;
* f3是encoding,|1111 0011|,根据上面的编码规则,可以知道这是个小整数,
* 0011需要再减1,得到2。
* 接下来是[02 f6],02是prevlen,表示上一entry占2个字节;
* f6是encoding,|1111 0110|,根据上面的编码规则,可以知道这是个小整数,
* 0110需要再减1,得到5。
* 最后,[ff]表示ziplist到此结束。
*
* 再演示怎么编码一个短字符串"Hello World"。
* 把下面这串东西插入到上面的[02 f6]后面:
*
* [02] [0b] [48 65 6c 6c 6f 20 57 6f 72 6c 64]
*
* [02]表示prevlen;[0b]是encoding |0000 1011|,前2bits表示这是个字符串,
* 后6bits表示字符串长度11字节;
* 后面的那串就是正文"Hello World"了。
*/
quicklist 泛型双重双向链表
基本特点
- 基于ziplist的扩展list,平衡了内存连续性和随机插入删除问题
- 支持对中间节点做压缩,依据是list通常只对表头表尾做操作而不是中间节点
源码位置
- quicklist.h
- quicklist.c
quicklistNode
- 32字节对齐
- 双向链表 有prev、next指针
- zl:指向一个ziplist或者指向一个quicklistLZF
- sz:存了ziplist的占用字节数,是压缩前大小
- count:记录ziplist里元素数量,不超过32k
- encodig:就是标识是否有压缩
- container:
- recompress:1表示这个node是暂时被解压缩出来使用的
- attempted_compress:1表示这个节点大小太小了,不能压缩
- extra:备用
quicklistLZF
typedef struct quicklistLZF {
unsigned int sz; /* LZF size in bytes*/
char compressed[];
} quicklistLZF;
- sz记录compressed字节长度,即压缩后大小
- 用LZF算法压缩
quicklist
typedef struct quicklist {
quicklistNode *head;
quicklistNode *tail;
unsigned long count; // 总entry个数
unsigned long len; // quicklistNodes个数
int fill : 16; // list-max-ziplist-size
unsigned int compress : 16; // list-compress-depth
} quicklist;
参考资料:
写作不易,您的支持是我写作的动力!