这次研究了一下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的更多相关文章

  1. redis 源代码分析(一) 内存管理

    一,redis内存管理介绍 redis是一个基于内存的key-value的数据库,其内存管理是很重要的,为了屏蔽不同平台之间的差异,以及统计内存占用量等,redis对内存分配函数进行了一层封装,程序中 ...

  2. Redis源代码分析(十一年)--- memtest内存测试

    今天,我们继续redis源代码test下测试在封装中的其它文件.今天读数memtest档,翻译了,那是,memory test 存储器测试工具..可是里面的提及了非常多东西,也给我涨了非常多见识,网上 ...

  3. Redis源代码分析(一)--Redis结构解析

    从今天起,本人将会展开对Redis源代码的学习,Redis的代码规模比較小,很适合学习,是一份很不错的学习资料,数了一下大概100个文件左右的样子,用的是C语言写的.希望终于能把他啃完吧,C语言好久不 ...

  4. Redis源代码分析(23)--- CRC循环冗余算法RAND随机数的算法

    他今天就开始学习Redis源代码的一些工具来实现,在任何一种语言工具.算法实现的原理应该是相同的,一些比較经典的算法.比方说我今天看的Crc循环冗余校验算法和rand随机数产生算法. CRC算法全称循 ...

  5. Redis源代码分析(六)--- ziplist压缩列表

    ziplist和之前我解析过的adlist列表名字看上去的非常像.可是作用却全然不同.之前的adlist主要针对的是普通的数据链表操作. 而今天的ziplist指的是压缩链表.为什么叫压缩链表呢.由于 ...

  6. Redis源代码分析(三)---dict哈希结构

    昨天分析完adlist的Redis代码.今天立即马不停蹄的继续学习Redis代码中的哈希部分的结构学习,只是在这里他不叫什么hashMap,而是叫dict.并且是一种全新设计的一种哈希结构,他仅仅是通 ...

  7. Redis源代码分析(二十八)--- object创建和释放redisObject物

    今天的学习更有效率.该Rio分析过,学习之间的另一种方式RedisObject文件,只想说RedisObject有些生成和转换.都是很类似的.列出里面长长的API列表: /* ------------ ...

  8. Redis源代码分析(十)--- testhelp.h小测试框架和redis-check-aof.c 日志检测

    周期分析struct结构体redis代码.最后,越多越发现很多的代码其实大同小异.于struct有袋1,2不分析文件,关于set集合的一些东西,就放在下次分析好了,在选择下个分析的对象时,我考虑了一下 ...

  9. Redis源代码分析(二十四)--- tool工具类(2)

    在上篇文章中初步的分析了一下,Redis工具类文件里的一些使用方法,包含2个随机算法和循环冗余校验算法,今天,继续学习Redis中的其它的一些辅助工具类的使用方法.包含里面的大小端转换算法,sha算法 ...

随机推荐

  1. C#总结项目《影院售票系统》编写总结三

    昨天总结了动态绘制控件.票类型的切换以及数据在窗体中的展现.今天继续总结,自己喜欢的就去做吧,让别人说去吧,省的自己再留下什么后悔遗憾,噢耶,加油! 今天总结项目中最核心的部分--购票.座位颜色状态的 ...

  2. LINQ Enumerable 续

    筛选序列 Enumerable.Distinct 对于复杂的对象列表,运行时引擎如何才能通过比较确定两个对象是否重复?对于复杂对象,必须提供一个比较器,即实现IEqualityComparer(Of ...

  3. 深入理解 CSS变形 transform(3d)

    坐标轴 在了解透视之前,首先要先了解坐标轴.3D变形与2D变形最大的不同就在于其参考的坐标轴不同.2D变形的坐标轴是平面的,只存在x轴和y轴,而3D变形的坐标轴则是x.y.z三条轴组成的立体空间,x轴 ...

  4. 如何使用CSS Sprites技术进行图片合并

    http://jingyan.baidu.com/article/066074d6757654c3c21cb02d.html

  5. 武汉科技大学ACM:1005: 华科版C语言程序设计教程(第二版)例题5.8

    Problem Description 老师给小豪出了一道题目:给你两个整数x和n(-10<=x<=10,1<=n<=10),让你求出x^1+x^2+x^3+……+x^n的结果 ...

  6. 请写一个C函数,判断处理器是大端存储还是小端存储,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1

    [解答] int checkCPU() { { union w { int a; char b; }c; c.a=1; return (c.b==1); } } [剖析] 嵌入式系统开发者应该对Lit ...

  7. [转]C++智能指针的创建

    zero 坐在餐桌前,机械的重复“夹菜 -> 咀嚼 -> 吞咽”的动作序列,脸上用无形的大字写着:我心不在焉.在他的对面坐着 Solmyr ,慢条斯理的吃着他那份午餐,维持着他一贯很有修养 ...

  8. android WIFI的一些属性

    package com.example.wifitest; import java.util.List; import android.content.Context; import android. ...

  9. FileZilla 无法保存密码

    当保存密码时遇到这个问题时: 解决办法: 一.找到FileZilla的设置: 二.点击"界面",取消勾选"不要保存密码",点击"确定"按钮, ...

  10. PHP扩展开发(5) - PHP常量的定义和读取

    1. 定义   //定义PHP常量REGISTER_STRINGL_CONSTANT("SIMPLE_VERSION", PHP_SIMPLE_VERSION, sizeof(PH ...