以下涉及到的源码均为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; } }