1. Adlist
Adlist 是一个通用的双向链表,其内部结点和链表的结构体如下:
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
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;
dup,free, match 分别是链表存储元素的复制,释放内存和比较函数的指针。Adlist的实现很简单,这里不再说明。
2. Zipmap
Zipmap 是一个用字符串数组来依次保存key-value 的map 类型,因此它不需要专门的结构体来保存数据。如果保存如下两个key-value对:"foo" => "bar","hello" => "world",其内存结构如下
<zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"
<zmlen> 是一字节长,保存map的当前大小(即key-value数),当 map 数大于等于254时,<zmlen>被置为255,表示map 大小需要遍历才能得到。
<len> 保存接下来的key-value字符串的长度(key 或者 value),<len>长度可以使1个字节或者5个字节,当<len> 第一个字节的值在0到252之间时,<len>是单字节长;当该值等于253时,后面接四个字节长的unsigned integer,该值为key-value字符串的长度。255被用来指示zipmap的结束符,254用于表示后续是一个空的内存空间,可以用来插入新的key-value对。
<free>保存后续的value字符串中没有被使用的字节数,比如当 ”foo” 被的value值先是”bar”,后续被改成”hi”,那么就有1个字节的空间多余。<free> 的值一定是个无符号的8字节值,因为当一次更新操作的value值大于原先的时,zipmap 会重新分配内存空间,以满足需要且尽可能的小。
上面的示例将会被压缩成如下的字符串数组:
"\x02\x03foo\x03\x00bar\x05hello\x05\x00world\xff"
下面我们来分析zipmap的主要几个API 接口函数,先来看如下几个函数:
unsigned char*zipmapNew(void)
unsigned char*zipmapSet(unsigned char *zm, unsigned char *key, unsigned int klen, unsignedchar *val, unsigned int vlen, int *update)
unsigned char*zipmapDel(unsigned char *zm, unsigned char *key, unsigned int klen, int*deleted);
unsigned char*zipmapNext(unsigned char *zm, unsigned char **key, unsigned int *klen,unsigned char **value, unsigned int *vlen);
intzipmapGet(unsigned char *zm, unsigned char *key, unsigned int klen, unsignedchar **value, unsigned int *vlen);
2.1 zipmapNew
/* Create a newempty zipmap. */
unsigned char*zipmapNew(void) {
unsigned char *zm = zmalloc(2);
zm[0] = 0; /* Length */
zm[1] = ZIPMAP_END;
return zm;
}
zipmapNew 用于创建一个zipmap结构体,包含两个字节,第一个字节<zmlen>是长度为1字节的无符号数,初始时置0;第二个字节初始置为ZIPMAP_END,表示zipmap的结束符。
2.2 zipmapSet
/* Set key tovalue, creating the key if it does not already exist.
* If 'update' is not NULL, *update is set to 1if the key was
* already preset, otherwise to 0. */
unsigned char*zipmapSet(unsigned char *zm, unsigned char *key, unsigned int klen, unsignedchar *val, unsigned int vlen, int *update) {
unsigned int zmlen, offset;
unsigned int freelen, reqlen =zipmapRequiredLength(klen,vlen);
unsigned int empty, vempty;
unsigned char *p;
freelen = reqlen;
if (update) *update = 0;
p =zipmapLookupRaw(zm,key,klen,&zmlen);
if (p == NULL) {
/* Key not found: enlarge */
zm = zipmapResize(zm, zmlen+reqlen);
p = zm+zmlen-1;
zmlen = zmlen+reqlen;
/* Increase zipmap length (this is aninsert) */
if (zm[0] < ZIPMAP_BIGLEN) zm[0]++;
} else {
/* Key found. Is there enough space forthe new value? */
/* Compute the total length: */
if (update) *update = 1;
freelen = zipmapRawEntryLength(p);
if (freelen < reqlen) {
/* Store the offset of this keywithin the current zipmap, so
* it can be resized. Then, movethe tail backwards so this
* pair fits at the current position. */
offset = p-zm;
zm = zipmapResize(zm,zmlen-freelen+reqlen);
p = zm+offset;
/* The +1 in the number of bytes tobe moved is caused by the
* end-of-zipmap byte. Note: the*original* zmlen is used. */
memmove(p+reqlen, p+freelen,zmlen-(offset+freelen+1));
zmlen = zmlen-freelen+reqlen;
freelen = reqlen;
}
}
/* We now have a suitable block where thekey/value entry can
* be written. If there is too much freespace, move the tail
* of the zipmap a few bytes to the frontand shrink the zipmap,
* as we want zipmaps to be very spaceefficient. */
empty = freelen-reqlen;
if (empty >= ZIPMAP_VALUE_MAX_FREE) {
/* First, move the tail <empty>bytes to the front, then resize
* the zipmap to be <empty> bytessmaller. */
offset = p-zm;
memmove(p+reqlen, p+freelen,zmlen-(offset+freelen+1));
zmlen -= empty;
zm = zipmapResize(zm, zmlen);
p = zm+offset;
vempty = 0;
} else {
vempty = empty;
}
/* Just write the key + value and we aredone. */
/* Key: */
p += zipmapEncodeLength(p,klen);
memcpy(p,key,klen);
p += klen;
/* Value: */
p += zipmapEncodeLength(p,vlen);
*p++ = vempty;
memcpy(p,val,vlen);
return zm;
}
该函数首先调用zipmapLookupRaw函数,在字符串中查找是否有该key,如果没有则分配一个key-value对大小的内存空间;如果找到,则判断新的value值的存储大小是否比原值要大,是的话调用zipmapResize函数重新分配zipmap的内存空间,并调用memmove平移以key值下一个key-value对为起始位置的字符串。此时,内存中有恰当的空间分配给key-value对,当空余的值大于ZIPMAP_VALUE_MAX_FREE宏时,我们就通过重新分配内存和平移来缩小字符串长度。最后,把key值和value值分别写入内存。
2.3 zipmapDel
/* Remove thespecified key. If 'deleted' is not NULL the pointed integer is
* set to 0 if the key was not found, to 1 ifit was found and deleted. */
unsigned char*zipmapDel(unsigned char *zm, unsigned char *key, unsigned int klen, int*deleted) {
unsigned int zmlen, freelen;
unsigned char *p = zipmapLookupRaw(zm,key,klen,&zmlen);
if (p) {
freelen = zipmapRawEntryLength(p);
memmove(p, p+freelen,zmlen-((p-zm)+freelen+1));
zm = zipmapResize(zm, zmlen-freelen);
/* Decrease zipmap length */
if (zm[0] < ZIPMAP_BIGLEN) zm[0]--;
if (deleted) *deleted = 1;
} else {
if (deleted) *deleted = 0;
}
return zm;
}
该函数删除key对应的key-value对,先调用zipmapLookupRaw函数查找key在zipmap的地址p,如果没找到则返回NULL;否则删除该key-value对,通过调用zipmapRawEntryLength先获得key-value的长度freelen,移动p+freelen到倒数第二个地址之间的内存数据,然后调用zipmapResize函数重新分配内存空间(相当于释放多余的内存空间),修改zm[0]值。
2.4 zipmapGet
/* Search a keyand retrieve the pointer and len of the associated value.
* If the key is found the function returns 1,otherwise 0. */
intzipmapGet(unsigned char *zm, unsigned char *key, unsigned int klen, unsignedchar **value, unsigned int *vlen) {
unsigned char *p;
if ((p = zipmapLookupRaw(zm,key,klen,NULL))== NULL) return 0;
p += zipmapRawKeyLength(p);
*vlen = zipmapDecodeLength(p);
*value = p + ZIPMAP_LEN_BYTES(*vlen) + 1;
return 1;
}
该函数也是先调用zipmapLookupRaw获得key所在的地址,如果为NULL,则返回0,表示没有获得value,否则调用zipmapRawKeyLength函数把地址定位到value的首地址,再通过zipmapDecodeLength得到value的长度,保存到*vlen中,最后把地址定位到存放value的位置,并保存到*value中。
2.5 zipmapLookupRaw
/* Search for amatching key, returning a pointer to the entry inside the
* zipmap. Returns NULL if the key is notfound.
*
* If NULL is returned, and totlen is not NULL,it is set to the entire
* size of the zimap, so that the callingfunction will be able to
* reallocate the original zipmap to make roomfor more entries. */
static unsignedchar *zipmapLookupRaw(unsigned char *zm, unsigned char *key, unsigned int klen,unsigned int *totlen) {
unsigned char *p = zm+1, *k = NULL;
unsigned int l,llen;
while(*p != ZIPMAP_END) {
unsigned char free;
/* Match or skip the key */
l = zipmapDecodeLength(p);
llen = zipmapEncodeLength(NULL,l);
if (key != NULL && k == NULL&& l == klen && !memcmp(p+llen,key,l)) {
/* Only return when the userdoesn't care
* for the total length of thezipmap. */
if (totlen != NULL) {
k = p;
} else {
return p;
}
}
p += llen+l;
/* Skip the value as well */
l = zipmapDecodeLength(p);
p += zipmapEncodeLength(NULL,l);
free = p[0];
p += l+1+free; /* +1 to skip the freebyte */
}
if (totlen != NULL) *totlen = (unsignedint)(p-zm)+1;
return k;
}
上述几个函数都需要调用zipmapLOokupRaw,函数查找Key值,返回指向zipmap相应key的地址,如果返回NULL,则表示没有找到。该函数通过while 循环和zipmapDecodeLength, zipmapEncodeLength等函数,依次判断每个zipmap中的每一个key值于函数参数的key值是否相等。函数中还传入一个totlen参数,当该参数不为NULL时,获取zipmap字符串数组的长度。