版本:redis - 5.0.4
参考资料:redis设计与实现
文件:src下的zipmap.c zipmap.h
zipmap与ziplist类似,主要目的就是节约内存
Memory layout of a zipmap, for the map "foo" => "bar", "hello" => "world":
<zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"
<zmlen>:长1个字节, 用于保存 zipmap 的当前大小。
当 zipmap 长度大于或等于254时, 需要遍历 zipmap 以找出长度。
<len>表示后面字符串的长度 (键或值)。
<len>:一字节或五字节长.
如果第一个字节 (作为无符号的8位值) 介于0和 253 之间, 它是一个单字节长度。
如果它是 254, 则后面四个字节为无符号整数 。
值255用于表示哈希的结尾.
<free>一般来表示后面的value长度的空闲值
<free>是字符串后的可用未使用字节数, 导致与键关联的值的修改。
例如, "foo" 将设置为 "hi", 它将有一个自由字节。
如果值稍后再次放大, 甚至再添加键值对, 只要free字节数够,就可以直接使用。
<free>始终是无符号8位数, 因为如果在更新操作后有多个可用字节, 将重新分配 zipmap, 以确保它尽可能小。
代码相对比较简单:
如果节点长度小于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);
}
}
}
//生成一个空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;
}
//生成一个
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);