redis之zipmap
2016-06-25 17:17
465 查看
类型介绍
zipmap也是redis的存储类型之一,这里面是sting->string的map结构,之所以出现这种形式的类型,主要是为了节省内存空间,采用的序列内存的方式,这种方式的一个问题就是查找的时候不是O(1)的复杂度,而是O(N)的时间复杂度,因此,它的目的是为了应付小规模的数据的,大规模的数据会过渡到hash表结构,这样应该是为了实现效率与空间的一种平衡;内存格式为:
zmlen–len–“foo”–len–free–“bar”–len–“hello”–len–free–“world”
zmlen,是指zipmap中包含的entry数目,大于254的话,就不起作用了,当做标记开头;
len,是指key的长度,小于254的话占据一个字节,超过的话,后面跟着4个字节来表示长度;
free,空位,一般不会超过4个
代码分析
变量
ZIPMAP_BIGLEN, zipmap的最大长度ZIPMAP_END, zipmap的结束符
ZIPMAP_VALUE_MAX_FREE, zipmap的free部分最大长度
函数
zipmapNew,新建一个zipmap;unsigned char *zipmapNew(void) { unsigned char *zm = zmalloc(2); zm[0] = 0; /* Length */ zm[1] = ZIPMAP_END; //结尾标志,0XFF return zm; }
zipmapDecodeLength,zipmap解码长度信息;
static unsigned int zipmapDecodeLength(unsigned char *p) { unsigned int len = *p; if (len < ZIPMAP_BIGLEN) return len; //如果长度小于254,无需编码 memcpy(&len,p+1,sizeof(unsigned int)); //长度超过255,获取p指针的后面的4个字节的数据 memrev32ifbe(&len); //获取该4个自己对应的数据,需要根据大小端判断,来解析长度信息 return len; }
zipmapEncodeLength,zipmap编码长度信息;
static unsigned int zipmapEncodeLength(unsigned char *p, unsigned int len) { if (p == NULL) { return ZIPMAP_LEN_BYTES(len); //如果p为NULL,则单纯返回该长度需要的内存字节数目 } else { if (len < ZIPMAP_BIGLEN) { p[0] = len; //如果小于254,那么无需编码,直接复制给当前字节 return 1; //主需要一个字节来保存长度信息 } else { p[0] = ZIPMAP_BIGLEN; memcpy(p+1,&len,sizeof(len)); memrev32ifbe(p+1); //需要硬编码到内存 return 1+sizeof(len); //需要5个字节来保存长度信息 } } }
zipmapLookupRaw,zipmap的查找过程;
static unsigned char *zipmapLookupRaw(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned int *totlen) { unsigned char *p = zm+1, *k = NULL; //zm+1是因为第一位是长度信息 unsigned int l,llen; while(*p != ZIPMAP_END) { //还没到结尾 unsigned char free; /* Match or skip the key */ l = zipmapDecodeLength(p); //获取当前地址对应的key长度 llen = zipmapEncodeLength(NULL,l); //获取该长度占用的字节数 if (key != NULL && k == NULL && l == klen && !memcmp(p+llen,key,l)) { //查看是否能够匹配key信息,如果匹配了 /* Only return when the user doesn't care * for the total length of the zipmap. */ if (totlen != NULL) { //这里表示是否需要返回zipmap的整体长度 k = p; } else { return p; //返回k/v当前地址 } } //下面是跳过当前kv,进入下一个kv p += llen+l; /* Skip the value as well */ l = zipmapDecodeLength(p); p += zipmapEncodeLength(NULL,l); free = p[0]; //free的长度 p += l+1+free; /* +1 to skip the free byte */ } if (totlen != NULL) *totlen = (unsigned int)(p-zm)+1; //如果需要返回zipmap占据的长度信息,计算 return k; }
zipmapRequiredLength,计算当前kv所需要的内存大小;
static unsigned long zipmapRequiredLength(unsigned int klen, unsigned 4000 int vlen) { unsigned int l; l = klen+vlen+3; //这三个是必有的, if (klen >= ZIPMAP_BIGLEN) l += 4; //如果超出254,还需要额外4个字节 if (vlen >= ZIPMAP_BIGLEN) l += 4; return l; }
zipmapRawKeyLength,计算当前key占用的内存大小,包括key的长度以及存储该长度使用的数目;
static unsigned int zipmapRawKeyLength(unsigned char *p) { unsigned int l = zipmapDecodeLength(p); //获取当前key的长度 return zipmapEncodeLength(NULL,l) + l; //返回当前key的长度l和该长度所需要的存储字节数之和 }
zipmapRawValueLength,计算存储当前value占用的所有内存数,包括value的长度以及free字段长度和存储value长度占用的字节数;
static unsigned int zipmapRawValueLength(unsigned char *p) { unsigned int l = zipmapDecodeLength(p); //获取当前value的长度 unsigned int used; used = zipmapEncodeLength(NULL,l); //获取存储value长度使用的内存数 used += p[used] + 1 + l; //加上一个free字段长度+1 return used; }
zipmapRawEntryLength,获取当前指针指向的一个kv占用的内存字节数;
static unsigned int zipmapRawEntryLength(unsigned char *p) { unsigned int l = zipmapRawKeyLength(p); //先计算当前key占用的字节数 return l + zipmapRawValueLength(p+l); //再计算当前value占用的字节数 }
zipmapResize,调整当前 zipmap的大小;
static inline unsigned char *zipmapResize(unsigned char *zm, unsigned int len) { zm = zrealloc(zm, len); zm[len-1] = ZIPMAP_END; //尾部置位 return zm; }
zipmapSet,设置kv,如果当前zipmap已存在该key,则更新对应的value值,这部分主要是涉及内存移动;
unsigned char *zipmapSet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char *val, unsigned int vlen, int *update) { unsigned int zmlen, offset; unsigned int freelen, reqlen = zipmapRequiredLength(klen,vlen); //计算该kv所需要的字节数 unsigned int empty, vempty; unsigned char *p; freelen = reqlen; if (update) *update = 0; //这个是用来表示该key是否之前已经存在,现在是更新操作 p = zipmapLookupRaw(zm,key,klen,&zmlen); //查找该key if (p == NULL) { /* Key not found: enlarge */ //未找到,扩大zipmap zm = zipmapResize(zm, zmlen+reqlen); p = zm+zmlen-1; //设置指针跳到zm+zmlen-1 zmlen = zmlen+reqlen; //当前的长度,zipmap占用的字节数 /* Increase zipmap length (this is an insert) */ if (zm[0] < ZIPMAP_BIGLEN) zm[0]++; //这里面是当zipmap的entry数目小于254才计数,多的话就不计数了,通过其他方式获取 } else { /* Key found. Is there enough space for the new value? */ /* Compute the total length: */ if (update) *update = 1; //更新操作 freelen = zipmapRawEntryLength(p); //获取该kv之前占据的内存字节数 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. */ offset = p-zm; //偏移量 zm = zipmapResize(zm, zmlen-freelen+reqlen); //调整大小 p = zm+offset; //跳转到现在zipmap之前entry的起始地址 /* The +1 in the number of bytes to be moved is caused by the * end-of-zipmap byte. Note: the *original* zmlen is used. */ memmove(p+reqlen, p+freelen, zmlen-(offset+freelen+1)); //将此前entry的后一个entry往后移动,空出来保存该entry的内存空间 zmlen = zmlen-freelen+reqlen; //获取新的zipmap的带下 freelen = reqlen; //设置新的该key占用的内存空间 } } /* We now have a suitable block where the key/value entry can * be written. If there is too much free space, move the tail * of the zipmap a few bytes to the front and shrink the zipmap, * as we want zipmaps to be very space efficient. */ empty = freelen-reqlen; //这个是更新操作时,现在的v的长度比以前的小 if (empty >= ZIPMAP_VALUE_MAX_FREE) { //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; //新的free值 } /* Just write the key + value and we are done. */ /* Key: */ //上面是指无论哪种情况,都是留出该kv需要的空间,并赋给p可以存储该信息的内存起始地址,下面就是依次将数据写入到内存中 p += zipmapEncodeLength(p,klen); memcpy(p,key,klen); p += klen; /* Value: */ p += zipmapEncodeLength(p,vlen); *p++ = vempty; memcpy(p,val,vlen); return zm; }
zipmapDel,删除指定key以及对应的内容;
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); //查找该key if (p) { //找到 freelen = zipmapRawEntryLength(p); //获取该key占用内存大小 memmove(p, p+freelen, zmlen-((p-zm)+freelen+1)); //将该kv后面的内容往前移动,覆盖待删除的kv zm = zipmapResize(zm, zmlen-freelen); //调整大小 /* Decrease zipmap length */ if (zm[0] < ZIPMAP_BIGLEN) zm[0]--; //如果zipmap的entry数目小于254,正常计数 if (deleted) *deleted = 1; //设置删除标记位 } else { if (deleted) *deleted = 0; } return zm; //返回现在的zipmap的首地址 }
zipmapNext,获取当前kv的值,并返回下一个entry的地址;
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; 直接到末尾了,返回NULL if (key) { //用于存放当前key的地址 *key = zm; *klen = zipmapDecodeLength(zm); //获取当前key的长度 *key += ZIPMAP_LEN_BYTES(*klen); //当前key的地址 } zm += zipmapRawKeyLength(zm); //移动到存储value的地址 if (value) { //用来保存当前value的地址 *value = zm+1; //跳过长度信息位 *vlen = zipmapDecodeLength(zm); //获取该value的长度 *value += ZIPMAP_LEN_BYTES(*vlen); //移动到value的地址 } zm += zipmapRawValueLength(zm); //移动到value之后,也就是下一个kv的开始地址 return zm; }
zipmapGet,获取某个key的value,如果没找到,返回0;
int zipmapGet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char **value, unsigned int *vlen) { unsigned char *p; if ((p = zipmapLookupRaw(zm,key,klen,NULL)) == NULL) return 0; //查找该key,没找到返回0 p += zipmapRawKeyLength(p); //找到跳过key的长度,移动到value部分 *vlen = zipmapDecodeLength(p); //获取该value的长度信息 *value = p + ZIPMAP_LEN_BYTES(*vlen) + 1; //跳到value内容的起始地址 return 1; }
zipmapLen,获取该zipmap的entry的长度;
unsigned int zipmapLen(unsigned char *zm) { unsigned int len = 0; if (zm[0] < ZIPMAP_BIGLEN) { len = zm[0]; //如果长度信息未超过254,则可以直接读取首位获取数目 } else { unsigned char *p = zipmapRewind(zm); //这个函数前面没列出,是用来跳过zipmap第一个字节的,直接是第一个key的首地址,和zipmapNext配合来遍历zipmap while((p = zipmapNext(p,NULL,NULL,NULL,NULL)) != NULL) len++; //一直到zipmap的结尾,获取长度 /* Re-store length if small enough */ if (len < ZIPMAP_BIGLEN) zm[0] = len; //如果发现长度信息比254小,则可以重置zipmap的第一个位,之所以可能出现这种情况是因为可能删除之前zipmap长度超了,删除过程没有恢复。 } return len; }
相关文章推荐
- redis安装问题小结
- 页面缓存:内存和文件之间的那些事
- 使用 Redis 和 Python 构建一个共享单车的应用程序
- Redis偶发连接失败案例实战记录
- Redis中实现查找某个值的范围
- win 7 安装redis服务【笔记】
- redis的hGetAll函数的性能问题(记Redis那坑人的HGETALL)
- Redis和Memcached的区别详解
- 分割超大Redis数据库例子
- Redis总结笔记(一):安装和常用命令
- Redis sort 排序命令详解
- 用Redis实现微博关注关系
- Redis实现信息已读未读状态提示
- redis中修改配置文件中的端口号 密码方法
- 在Ruby on Rails上使用Redis Store的方法
- 浅析SQL Server中的执行计划缓存(上)
- Enterprise Library for .NET Framework 2.0缓存使用实例
- Redis和Memcache的区别总结
- PowerShell中编程清空IE缓存方法
- PowerShell中使用.NET将程序集加入全局程序集缓存