前沿
在上一篇中,我们着重分析了sds字符串类型;了解该类型的底层数据结构,以及如何实现动态内存分配
本篇主要内容
在本篇中,学习下zipmap数据类型,以及对应的一系列基础操作函数(查找、set、get)
- zipmap的内存布局
zmlen|key length|key|value length|free|value|…|end –|:–:|–:|:–:|–:|:–:|:–:|:–:
-
zmelen: 1字节,记录当前zipmap中key-value对的数量;由于zmlen只有1个字节,因此规定其表示的数量只能为0~254,当mzlen>254时,就需要遍历整个zipmap来得到key-value对的个数
-
len:用于记录key或value的长度,有两种情况
-
当len的第一个字节为0~253时,那么len就只占用这一个字节
-
当len的第一个字节为254时,那么真实len将用后面的4个字节来表示
-
因此len要么占用1字节,要么占用5字节
-
-
free: 1字节,表示随后的value后面的空闲字节数,这主要是改变key的value引起的,当free的字节数过大用1个字节不足以表示时,zipmap就会重新分配内存,保证字符串尽量紧凑。
-
end:1个字节,为0xFF,用于标志zipmap的结束
代码分析
创建zipmap
/* 对于一个空的zipmap只有2个字节,1个字节的zmlen=0,1一个字节的end=0xFF */
unsigned char *zipmapNew(void) {
unsigned char *zm = zmalloc(2);
zm[0] = 0; /* Length */
zm[1] = ZIPMAP_END;
return zm;
}
zipmapSet
/* 在zipmap结构中增加一对key-value.
* 1. 如果key已经在zipmap中存在,则检测原有的key-value entry总长度能否满足新数据的长度要求
a. 不行则先将zipmap扩容,将p指针定位到key起始点
* 2. 如果key在zipmap中不存在,则扩容zipmap,并将p指针指到新扩容的起始位置
* 3. 检测freedata长度是否超过254,如果是则要收缩zipmap
* 4. 将新的key,value数据更新到zipmap中,从p指针指向位置开始
* . */
unsigned char *zipmapSet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char *val, unsigned int vlen, int *update) {
unsigned int zmlen, offset;
/* 根据klen, vlen确定需要申请的内存大小,默认:1(klen)+1(vlen)+1(free) + klen + vlen */
unsigned int freelen, reqlen = zipmapRequiredLength(klen,vlen);
unsigned int empty, vempty;
unsigned char *p;
freelen = reqlen;
if (update) *update = 0;
/* 遍历zipmap中所有key-value,确定key是否已存在;如果存在则返回key地址偏移量;
* 看明白附录中的几个辅助函数再来理解逻辑很清晰 */
p = zipmapLookupRaw(zm,key,klen,&zmlen);
if (p == NULL) {
/* key没有存在,扩充zipmap,容纳新键值对 */
zm = zipmapResize(zm, zmlen+reqlen);
p = zm+zmlen-1;
zmlen = zmlen+reqlen;
/* 如果zipmap中key-value对数量没有达到254,则递增 */
if (zm[0] < ZIPMAP_BIGLEN) zm[0]++;
} else {
/* Key found. Is there enough space for the new value? */
/* Compute the total length: */
if (update) *update = 1;
/* 计算原有的key-value entry总长度, p指针指向的是key的起始地址 */
freelen = zipmapRawEntryLength(p);
if (freelen < reqlen) {
/* Store the offset of this key within the current zipmap, so
* it can be resized. Then, move the tail backwards so this
* pair fits at the current position. */
/* 获取该key相对zm结构体首地址偏移量 */
offset = p-zm;
/* 如果没有空间插入新的值,则调整大小 */
zm = zipmapResize(zm, zmlen-freelen+reqlen);
/* 将p指针定位到重复key起始点 */
p = zm+offset;
/* 将p指针后留出reqlen长度空间;把原有的数据向后mov */
memmove(p+reqlen, p+freelen, zmlen-(offset+freelen+1));
zmlen = zmlen-freelen+reqlen;/* 更新zmlen长度 */
freelen = reqlen;/* 更新freelen为新的key-value entry总长度 */
}
}
/* 检测空闲数据区是否超过ZIPMAP_VALUE_MAX_FREE,如果超过则要缩小,使得zipmap空间紧凑 */
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> bytes smaller. */
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;
}
/* 将新的key,value数据更新到zipmap中 */
/* Key: */
p += zipmapEncodeLength(p,klen);
memcpy(p,key,klen);
p += klen;
/* Value: */
p += zipmapEncodeLength(p,vlen);
*p++ = vempty;
memcpy(p,val,vlen);
return zm;
}
zipmapGet
/* 获取指定key的value信息
* value:返回指针指向value data首地址;vlen:返回value data长度
* 1. zipmapLookupRaw:找到指定key的起始位置
*/
int zipmapGet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char **value, unsigned int *vlen) {
unsigned char *p;
/* 遍历zipmap中所有key-value,确定key是否已存在;如果存在则返回key地址偏移量;
* 看明白附录中的几个辅助函数再来理解逻辑很清晰 */
if ((p = zipmapLookupRaw(zm,key,klen,NULL)) == NULL) return 0;
p += zipmapRawKeyLength(p); /* 将p指针指向value域的起始地址 */
*vlen = zipmapDecodeLength(p); /* vlen:指向value length起始地址 */
*value = p + ZIPMAP_LEN_BYTES(*vlen) + 1; /* 将value指针指向value data首地址 */
return 1;
}
zipmapNext
/* 迭代zm中key-value对
* 1. 如果zm[0]=0xff则结束
* 2. zm每次定位到一个新的key-value起始地址
*/
unsigned char *zipmapNext(unsigned char *zm, unsigned char **key, unsigned int *klen, unsigned char **value, unsigned int *vlen) {
if (zm[0] == ZIPMAP_END) return NULL;
if (key) {
*key = zm;
*klen = zipmapDecodeLength(zm);
*key += ZIPMAP_LEN_BYTES(*klen);
}
zm += zipmapRawKeyLength(zm);
if (value) {
*value = zm+1;
*vlen = zipmapDecodeLength(zm);
*value += ZIPMAP_LEN_BYTES(*vlen);
}
zm += zipmapRawValueLength(zm);
return zm;
}
附录
zipmapEncodeLength
长度信息编码
/* 通用函数,计算key,value的长度及在相应位置填入数据
* 1. 值小于0xFE,则只有8位表示长度,且内容就是长度值
* 2. 长度大于0xFE,则有40位表示长度信息,其前8位内容是0xFE,后32位是值内容
*/
static unsigned int zipmapEncodeLength(unsigned char *p, unsigned int len) {
if (p == NULL) {
/* 如果第一个参数传NULL,根据第二个参数决定需要多长的空间去存储长度信息 */
return ZIPMAP_LEN_BYTES(len);
} else {
/* 如果第一个参数有值,则根据第二个参数决定在该地址的不同偏移位置设置相应的值 */
if (len < ZIPMAP_BIGLEN) {
p[0] = len;
return 1;
} else {
p[0] = ZIPMAP_BIGLEN;
memcpy(p+1,&len,sizeof(len));
memrev32ifbe(p+1);
return 1+sizeof(len);
}
}
}
zipmapDecodeLength
长度信息解码,是编码函数的逆向操作
/* 通用函数,计算data的长度
* 判断传入的长度信息起始地址的内容是否小于0xFE。如果是则该8位就是长度值;否则后移8位,之后的32位才是长度值
*/
static unsigned int zipmapDecodeLength(unsigned char *p) {
unsigned int len = *p;
if (len < ZIPMAP_BIGLEN) return len;
memcpy(&len,p+1,sizeof(unsigned int));
memrev32ifbe(&len);
return len;
}
zipmapRawKeyLength
/* 计算keylen+keydata len长度和
* 通过获取的KeyData长度计算出KeyLen Struct的长度,然后将两个长度相加
*/
static unsigned int zipmapRawKeyLength(unsigned char *p) {
unsigned int l = zipmapDecodeLength(p);
return zipmapEncodeLength(NULL,l) + l;
}
zipmapRawValueLength
/* 计算vallen + free len +value data len+free data len长度和
* 通过获取的KeyData长度计算出KeyLen Struct的长度,然后将两个长度相加
*/
static unsigned int zipmapRawValueLength(unsigned char *p) {
unsigned int l = zipmapDecodeLength(p);
unsigned int used;
/* used=valuelen struct长度, p[used]=free内容,即freedata长度 */
used = zipmapEncodeLength(NULL,l);
used += p[used] + 1 + l;//valuelen +=free data len + free len + value date len
return used;
}
zipmapRawEntryLength
/* 计算一对key-value entry的长度 */
static unsigned int zipmapRawEntryLength(unsigned char *p) {
unsigned int l = zipmapRawKeyLength(p);
/* 让指针指向value信息首地址,计算vlaue所有信息的长度 */
return l + zipmapRawValueLength(p+l);
}