Redis源代码分析-内存数据结构intset
这次研究了一下intset。研究的过程中,一度看不下过去,可是还是咬牙挺过来了。看懂了也就是那么回事。静下心来,切莫浮躁
Redis为了追求高效,在存储下做了非常多的优化,像intset就是作者为了节约内存定制的数据结构,包含后面将要阅读的压缩列表。
intset是一个有序的整数集,提供了添加,删除,查找的接口,针对uint16_t uint32_t uint64_t,提供了不同编码的转换(严格的说仅仅是类型的提升)
首先。看一下它的结构定义:
typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;
encoding:有例如以下几种编码
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
实际上这里使用一个uint8_t存储就够了
length:当前整数集有多少个整数
contents[]:详细存储的位置。这里以一个字节为存储单元,方便对高类型进行寻址
看一下它对外提供的接口:
intset *intsetNew(void);
intset *intsetAdd(intset *is, int64_t value, uint8_t *success);
intset *intsetRemove(intset *is, int64_t value, int *success);
uint8_t intsetFind(intset *is, int64_t value);
int64_t intsetRandom(intset *is);
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
uint32_t intsetLen(intset *is);
size_t intsetBlobLen(intset *is);
一种数据结构。必定要提供类似插入。查询。删除这种接口。另外不要暴露内部使用的接口,这里提供的接口,我们详细分析几个
初始化接口:
/* Create an empty intset. */
intset *intsetNew(void) {
intset *is = malloc(sizeof(intset));
is->encoding = intrev32ifbe(INTSET_ENC_INT16);
is->length = 0;
return is;
}
没什么难的,注意默认使用最低的2字节存储
/* Insert an integer in the intset */
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
uint8_t valenc = _intsetValueEncoding(value);
uint32_t pos;
if (success) *success = 1; /* Upgrade encoding if necessary. If we need to upgrade, we know that
* this value should be either appended (if > 0) or prepended (if < 0),
* because it lies outside the range of existing values. */
if (valenc > intrev32ifbe(is->encoding)) {
/* This always succeeds, so we don't need to curry *success. */
return intsetUpgradeAndAdd(is,value);
} else {
/* Abort if the value is already present in the set.
* This call will populate "pos" with the right position to insert
* the value when it cannot be found. */
if (intsetSearch(is,value,&pos)) {
if (success) *success = 0;
return is;
} is = intsetResize(is,intrev32ifbe(is->length)+1);
if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
} _intsetSet(is,pos,value);
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}
这个接口比較有难度。详细分析:
1、首先推断要添加的值的编码是否大于当前编码,大于则进行类型提升。并添加value
2、假设小于当前编码,首先查询数据是否存在,存在则返回,不存在则设置插入位置pos
3、又一次分配内存大小
4、移动数据。全部数据往后移动。复杂度有点高啊
5、插入数据,设置数据个数
当中。类型提升并插入value的接口例如以下:
/* Upgrades the intset to a larger encoding and inserts the given integer. */
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
uint8_t curenc = intrev32ifbe(is->encoding);
uint8_t newenc = _intsetValueEncoding(value);
int length = intrev32ifbe(is->length);
int prepend = value < 0 ? 1 : 0; /* First set new encoding and resize */
is->encoding = intrev32ifbe(newenc);
is = intsetResize(is,intrev32ifbe(is->length)+1); /* Upgrade back-to-front so we don't overwrite values.
* Note that the "prepend" variable is used to make sure we have an empty
* space at either the beginning or the end of the intset. */
while(length--)
_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc)); /* Set the value at the beginning or the end. */
if (prepend)
_intsetSet(is,0,value);
else
_intsetSet(is,intrev32ifbe(is->length),value);
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}
能够看到,类型提升的步骤例如以下:
1、由于整数集是有序的,所以首先推断要加入的数是正数还是负数,正数就在尾部加入,负数则在头部加入
2、添加内存大小
3、移动数据,这里和第一步挂钩。并且移动的过程比較难以理解,首先依据原来编码取出数据,然后依据新的编码插入数据
4、插入数据。在头部还是尾部插入
5、改动数据个数
另外移动数据的接口例如以下:
static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) {
void *src, *dst;
uint32_t bytes = intrev32ifbe(is->length)-from;
uint32_t encoding = intrev32ifbe(is->encoding); if (encoding == INTSET_ENC_INT64) {
src = (int64_t*)is->contents+from;
dst = (int64_t*)is->contents+to;
bytes *= sizeof(int64_t);
} else if (encoding == INTSET_ENC_INT32) {
src = (int32_t*)is->contents+from;
dst = (int32_t*)is->contents+to;
bytes *= sizeof(int32_t);
} else {
src = (int16_t*)is->contents+from;
dst = (int16_t*)is->contents+to;
bytes *= sizeof(int16_t);
}
memmove(dst,src,bytes);
}
由于是连续的内存,找到移动的起始位置,然后memmove(),bingo!
!!
查找数据的接口实现:
static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
int min = 0, max = intrev32ifbe(is->length)-1, mid = -1;
int64_t cur = -1; /* The value can never be found when the set is empty */
if (intrev32ifbe(is->length) == 0) {
if (pos) *pos = 0;
return 0;
} else {
/* Check for the case where we know we cannot find the value,
* but do know the insert position. */
if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {
if (pos) *pos = intrev32ifbe(is->length);
return 0;
} else if (value < _intsetGet(is,0)) {
if (pos) *pos = 0;
return 0;
}
} while(max >= min) {
mid = ((unsigned int)min + (unsigned int)max) >> 1;
cur = _intsetGet(is,mid);
if (value > cur) {
min = mid+1;
} else if (value < cur) {
max = mid-1;
} else {
break;
}
} if (value == cur) {
if (pos) *pos = mid;
return 1;
} else {
if (pos) *pos = min;
return 0;
}
}
还是个二分查找,niubility!!
!个人感觉这样的数据结构的高效就体如今这里。由于是有序。所以查找高速,由于是数组。所以插入。删除。是连续内存拷贝,也非常快
有时间突然想去看一下STL Vector的实现了,它的insert是怎样实现的?
Redis源代码分析-内存数据结构intset的更多相关文章
- redis 源代码分析(一) 内存管理
一,redis内存管理介绍 redis是一个基于内存的key-value的数据库,其内存管理是很重要的,为了屏蔽不同平台之间的差异,以及统计内存占用量等,redis对内存分配函数进行了一层封装,程序中 ...
- Redis源代码分析(十一年)--- memtest内存测试
今天,我们继续redis源代码test下测试在封装中的其它文件.今天读数memtest档,翻译了,那是,memory test 存储器测试工具..可是里面的提及了非常多东西,也给我涨了非常多见识,网上 ...
- Redis源代码分析(一)--Redis结构解析
从今天起,本人将会展开对Redis源代码的学习,Redis的代码规模比較小,很适合学习,是一份很不错的学习资料,数了一下大概100个文件左右的样子,用的是C语言写的.希望终于能把他啃完吧,C语言好久不 ...
- Redis源代码分析(23)--- CRC循环冗余算法RAND随机数的算法
他今天就开始学习Redis源代码的一些工具来实现,在任何一种语言工具.算法实现的原理应该是相同的,一些比較经典的算法.比方说我今天看的Crc循环冗余校验算法和rand随机数产生算法. CRC算法全称循 ...
- Redis源代码分析(六)--- ziplist压缩列表
ziplist和之前我解析过的adlist列表名字看上去的非常像.可是作用却全然不同.之前的adlist主要针对的是普通的数据链表操作. 而今天的ziplist指的是压缩链表.为什么叫压缩链表呢.由于 ...
- Redis源代码分析(三)---dict哈希结构
昨天分析完adlist的Redis代码.今天立即马不停蹄的继续学习Redis代码中的哈希部分的结构学习,只是在这里他不叫什么hashMap,而是叫dict.并且是一种全新设计的一种哈希结构,他仅仅是通 ...
- Redis源代码分析(二十八)--- object创建和释放redisObject物
今天的学习更有效率.该Rio分析过,学习之间的另一种方式RedisObject文件,只想说RedisObject有些生成和转换.都是很类似的.列出里面长长的API列表: /* ------------ ...
- Redis源代码分析(十)--- testhelp.h小测试框架和redis-check-aof.c
日志检测
周期分析struct结构体redis代码.最后,越多越发现很多的代码其实大同小异.于struct有袋1,2不分析文件,关于set集合的一些东西,就放在下次分析好了,在选择下个分析的对象时,我考虑了一下 ...
- Redis源代码分析(二十四)--- tool工具类(2)
在上篇文章中初步的分析了一下,Redis工具类文件里的一些使用方法,包含2个随机算法和循环冗余校验算法,今天,继续学习Redis中的其它的一些辅助工具类的使用方法.包含里面的大小端转换算法,sha算法 ...
随机推荐
- css基础回顾-定位:position
w3school 对position定义和说明是: 定义和用法: position 属性规定元素的定位类型. 说明: 这个属性定义建立元素布局所用的定位机制.任何元素都可以定位,不过绝对或固定元素会生 ...
- javascript代码放置位置对程序的影响
在编写html文档时,javascript可以放置的位置有两个地方<head>或者<body>,但是放置的地方,会对 JavaScript 代码的正常执行会有一定影响.由于 H ...
- Python正则表达式一
推荐 http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html#!comments 这篇博客超好,建议收藏. 不过对于正则表达式小白,他没 ...
- YOU邮件
include("class.phpmailer.php");include("class.smtp.php");//获取一个外部文件的内容$mail ...
- 一行代码实现C#的四舍五入
C# 使用的是”四舍六入五成双”的银行家算法: 1 2 Math.Round(2.5); // 2 Math.Round(1.5); // 2 由此可见,1.5的Round符合我们的四舍五入,于是Ha ...
- 3.1 as86汇编器
在开始讲述as86汇编器前,这本书引用内核中bootsect.s框架程序汇编代码来解释,记录下这一小段代码中不理解的地方,下面是这段实例代码: .globl begtext, begdata, beg ...
- js插件zClip实现复制到剪贴板功能
相信这个功能大家平时上网经常能碰到,以前也没怎么留意怎么实现的,直到项目中需要. 网上一搜一大堆,单纯使用js方法也不是没有,但是由于各浏览器的安全机制不同,不是跨浏览器的.去看了几个常用的网站,都是 ...
- PHPCMSv9 更改后台地址(测试)
最新发布的PHPCMS V9由于采用了MVC的设计模式,所以它的后台访问地址是固定的,虽然可以通过修改路由配置文件来实现修改,但每次都修改路由配置文件对于我来说有点麻烦了,而且一不小心就会出错.这里使 ...
- 你不知的IE的bug及其解决方案
E令人咬牙切齿的bug不胜枚举,其中IE6更是臭名昭著,令人发指.这里总结出IE下最为严重的5个bug,及其应对方案. 1.IE6下无法显示png格式的透明信息 这个bug是众多网页设计师的噩梦,虽然 ...
- dede仿站笔记
仿站步骤查看是否为dedecms的方法,看引用路径src="/templets/default2012/images/toutiao.png" 0查看仿站编码,选择utf8或gbk ...