Redis 源码阅读二:adlist/zipmap

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字符串数组的长度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值