0%

Redis源码剖析之整数集合

以下涉及到的源码均为redis5.0-rc3版本的代码【点击查看官方源码

整数集合

整数集合(intset),故名思意,是存放整数的集合数据结构,从而可知其是集合(Set)的底层实现之一。而从源码中可知,整数集合中的元素又是从小到大来排序的。

数据结构

整数集合的定义在源码头文件intset.h中,如下所示:

1
2
3
4
5
typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;
  • encoding:当前结构内所有元素的类型,如int8_t,int16_t,int32_t,int64_t;
  • length:contents数组中元素的个数,也即一个整数集合中存储的元素的个数;
  • contents[]:存放元素的数组;

类型升级

整数集合结构中,既然用了encoding字段来标记结构中存储的元素的类型,那么也就会出现类型变动。因为使用者在向集合中添加新元素的时候,并不知道其类型究竟是16位、32位或者又是64位的,所以需要动态的来进行类型升级,并且只升不降。
如下是在intset.c文件中对类型所占字节长度的计算函数的宏定义:

1
2
3
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

下面是升级操作的源码,并且在升级的过程中将要添加的元素也进行了添加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* 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;
}

static intset *intsetResize(intset *is, uint32_t len) {
uint32_t size = len*intrev32ifbe(is->encoding);
is = zrealloc(is,sizeof(intset)+size);
return is;
}

API

在intset.h头文件中定义了如下API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//创建新的整数集合
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);
//get给定下标的值
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
//元素总个数
uint32_t intsetLen(const intset *is);
//集合占用的内存字节
size_t intsetBlobLen(intset *is);

整体性的概览的了整数集合的API,那么接下来就来看一下源码中api的具体内部是怎么实现的。

intsetNew

1
2
3
4
5
6
7
8
9
/* Create an empty intset. */
intset *intsetNew(void) {
//分配内,创建一个空的整数集合
intset *is = zmalloc(sizeof(intset));
//设置编码
is->encoding = intrev32ifbe(INTSET_ENC_INT16);
is->length = 0;
return is;
}

intsetAdd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
//确定当前要添加的元素的类型长度
uint8_t valenc = _intsetValueEncoding(value);
uint32_t pos;
if (success) *success = 1;
if (valenc > intrev32ifbe(is->encoding)) {
//升级,并完成元素添加
return intsetUpgradeAndAdd(is,value);
} else {
//确定是否已存在该元素
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;
}

intsetRemove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
intset *intsetRemove(intset *is, int64_t value, int *success) {
uint8_t valenc = _intsetValueEncoding(value);
uint32_t pos;
if (success) *success = 0;

if (valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,&pos)) {
uint32_t len = intrev32ifbe(is->length);

/* We know we can delete */
if (success) *success = 1;

/* Overwrite value with tail and update length */
if (pos < (len-1)) intsetMoveTail(is,pos+1,pos);
is = intsetResize(is,len-1);
is->length = intrev32ifbe(len-1);
}
return is;
}

intsetFind,intsetRandom,intsetGet,intsetLen,intsetBlobLen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
uint8_t intsetFind(intset *is, int64_t value) {
uint8_t valenc = _intsetValueEncoding(value);
return valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,NULL);
}

int64_t intsetRandom(intset *is) {
return _intsetGet(is,rand()%intrev32ifbe(is->length));
}

uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value) {
if (pos < intrev32ifbe(is->length)) {
*value = _intsetGet(is,pos);
return 1;
}
return 0;
}

uint32_t intsetLen(const intset *is) {
return intrev32ifbe(is->length);
}

size_t intsetBlobLen(intset *is) {
return sizeof(intset)+intrev32ifbe(is->length)*intrev32ifbe(is->encoding);
}

内部函数

这是来看一下上面api中一些重要的内部调用函数的实现,在看这些函数的时候可以进一步来看api的实现。
intsetMoveTail是在添加元素和删除元素中都会用到的一个方法,其作用在于集合中元素的移动。通过从一个结构中把元素从另一个结构中的方式来完成操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
}

intsetSearch在查询、插入、删除等api中用到,用于查询确认元素的具体索引位置。当找到了对应的元素时,pos为索引,函数返回1;否则函数返回0,且pos代表可以进行插入元素的索引位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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;

if (intrev32ifbe(is->length) == 0) {
//当集合没有元素的时候,可插入索引0
if (pos) *pos = 0;
return 0;
} else {
//当没有指定的元素情况下,确定可插入元素的位置
if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {
//确认元素可插入在最后
if (pos) *pos = intrev32ifbe(is->length);
return 0;
} else if (value < _intsetGet(is,0)) {
//确认元素可插入在第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;
}
}

_intsetSet函数是用来set值的,在添加元素和升级操作中用到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void _intsetSet(intset *is, int pos, int64_t value) {
uint32_t encoding = intrev32ifbe(is->encoding);

if (encoding == INTSET_ENC_INT64) {
((int64_t*)is->contents)[pos] = value;
memrev64ifbe(((int64_t*)is->contents)+pos);
} else if (encoding == INTSET_ENC_INT32) {
((int32_t*)is->contents)[pos] = value;
memrev32ifbe(((int32_t*)is->contents)+pos);
} else {
((int16_t*)is->contents)[pos] = value;
memrev16ifbe(((int16_t*)is->contents)+pos);
}
}

_intsetGetEncoded通过给定pos和encoding得到指定值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static int64_t _intsetGetEncoded(intset *is, int pos, uint8_t enc) {
int64_t v64;
int32_t v32;
int16_t v16;

if (enc == INTSET_ENC_INT64) {
memcpy(&v64,((int64_t*)is->contents)+pos,sizeof(v64));
memrev64ifbe(&v64);
return v64;
} else if (enc == INTSET_ENC_INT32) {
memcpy(&v32,((int32_t*)is->contents)+pos,sizeof(v32));
memrev32ifbe(&v32);
return v32;
} else {
memcpy(&v16,((int16_t*)is->contents)+pos,sizeof(v16));
memrev16ifbe(&v16);
return v16;
}
}