redis源码解析(五)——zipmap

版本:redis - 5.0.4
参考资料:redis设计与实现
文件:src下的zipmap.c zipmap.h

一、注释

zipmap与ziplist类似,主要目的就是节约内存

1.存储方式:

Memory layout of a zipmap, for the map "foo" => "bar", "hello" => "world":
<zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"

2.各个值的意思

	<zmlen>:长1个字节, 用于保存 zipmap 的当前大小。
	当 zipmap 长度大于或等于254, 需要遍历 zipmap 以找出长度。
	 
	<len>表示后面字符串的长度 (键或值)<len>:一字节或五字节长.
	如果第一个字节 (作为无符号的8位值) 介于0253 之间, 它是一个单字节长度。
	如果它是 254, 则后面四个字节为无符号整数 。
	值255用于表示哈希的结尾.

	<free>一般来表示后面的value长度的空闲值
	<free>是字符串后的可用未使用字节数, 导致与键关联的值的修改。
	例如, "foo" 将设置为 "hi", 它将有一个自由字节。
	如果值稍后再次放大, 甚至再添加键值对, 只要free字节数够,就可以直接使用。

	<free>始终是无符号8位数, 因为如果在更新操作后有多个可用字节, 将重新分配 zipmap, 以确保它尽可能小。

二、zipmap.c

代码相对比较简单:

1、len

如果节点长度小于254,即一字节,则len属性也就只有一字节长
如果大于等于254,则len属性为五字节长,第一个字节设为0xFE(十进制的254),后四字节存储长度

//返回编码整数l需要的字节数
//如果len小于254则为1字节,大于等于254则为五字节
#define ZIPMAP_LEN_BYTES(_l) (((_l) < ZIPMAP_BIGLEN) ? 1 : sizeof(unsigned int)+1)

//如果节点长度小于254,即一字节,则len属性也就只有一字节长
//如果大于等于254,则len属性为五字节长,第一个字节设为0xFE(十进制的254),后四字节存储长度
static unsigned int zipmapDecodeLength(unsigned char *p) {
    unsigned int len = *p;

    //254
    if (len < ZIPMAP_BIGLEN) return len;
    memcpy(&len,p+1,sizeof(unsigned int));
    memrev32ifbe(&len);
    return len;
}

/* Encode the length 'l' writing it in 'p'. If p is NULL it just returns
 * the amount of bytes required to encode such a length. */
 //把长为l的写到p中,若p为null则返回编码l所需长度
static unsigned int zipmapEncodeLength(unsigned char *p, unsigned int len) {
    if (p == NULL) {
        return ZIPMAP_LEN_BYTES(len);
    } else {
        if (len < ZIPMAP_BIGLEN) {
            p[0] = len;
            return 1;
        } else {
        	//第一个字节设为0xFE(十进制的254),后四字节存储长度
            p[0] = ZIPMAP_BIGLEN;
            memcpy(p+1,&len,sizeof(len));
            memrev32ifbe(p+1);
            return 1+sizeof(len);
        }
    }
}

2、zm

//生成一个空map
unsigned char *zipmapNew(void) {
    unsigned char *zm = zmalloc(2);

    zm[0] = 0;//长度
    zm[1] = ZIPMAP_END;//设置末尾,255
    return zm;
}

static unsigned char *zipmapLookupRaw(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned int *totlen) {
    //p指向zm的下一地址,若map为空,则只有zm[0],zm[1],
    //那么就指向了zm[1],即map末尾zlend(值为ZIPMAP_END)
    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 user doesn't care
             * for the total length of the zipmap. */
            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 free byte */
    }
    //循环完后,p指向了map末尾,所以重新设置len,节约内存
    if (totlen != NULL) *totlen = (unsigned int)(p-zm)+1;
    return k;
}

3、API

//生成一个
unsigned char *zipmapNew(void);
//设置
unsigned char *zipmapSet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char *val, unsigned int vlen, int *update);
//删除
unsigned char *zipmapDel(unsigned char *zm, unsigned char *key, unsigned int klen, int *deleted);
//遍历(zipmapNext)之前先从zm[0]移至第一个键值对处
unsigned char *zipmapRewind(unsigned char *zm);
//得到下一个
unsigned char *zipmapNext(unsigned char *zm, unsigned char **key, unsigned int *klen, unsigned char **value, unsigned int *vlen);
//得到键值对
int zipmapGet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char **value, unsigned int *vlen);
//key是否存在
int zipmapExists(unsigned char *zm, unsigned char *key, unsigned int klen);
//长度
unsigned int zipmapLen(unsigned char *zm);
//返回 zipmap 的原始大小
size_t zipmapBlobLen(unsigned char *zm);
//打印p指向的键值对
void zipmapRepr(unsigned char *p);

你可能感兴趣的:(源码分析)