目录
整数集合IntSet结构设计
IntSet的升级操作
升级具体过程
升级具体源码
小结
IntSet是Redis中set集合的一种实现方式,基于整数数组来实现,并且具备长度可变、有序等特征。
整数集合IntSet结构设计
整数集合本质上是一块连续内存空间,它的结构定义如下:
typedef struct intset {
//编码方式,控制实质数据开辟为多大
uint32_t encoding;
//集合包含的元素数量
uint32_t length;
//声明一个保存元素的数组
int8_t contents[];
} intset;
可以看到,保存元素的容器是一个 contents 数组,虽然 contents 被声明为 int8_t 类型的数组,但是实际上 contents 数组并不保存任何 int8_t 类型的元素,contents 数组的真正类型取决于 intset 结构体里的 encoding 属性的值。比如:
- 如果 encoding 属性值为 INTSET_ENC_INT16,那么 contents 就是一个 int16_t 类型的数组,其中数组中每一个元素的类型都是 int16_t;
- 如果 encoding 属性值为 INTSET_ENC_INT32,那么 contents 就是一个 int32_t 类型的数组,其中数组中每一个元素的类型都是 int32_t;
- 如果 encoding 属性值为 INTSET_ENC_INT64,那么 contents 就是一个 int64_t 类型的数组,其中数组中每一个元素的类型都是 int64_t;
为了方便查找,Redis源码中通过intsetSearch进行二分查找会将intset中所有的整数按照升序依次保存在contents数组中,结构如图:
IntSet的升级操作
整数集合会有一个升级规则,就是当我们将一个新元素加入到整数集合里面,如果新元素的类型(int32_t)比整数集合现有所有元素的类型(int16_t)都要长时,整数集合需要先进行升级,也就是按新元素的类型(int32_t)扩展 contents 数组的空间大小,然后才能将新元素加入到整数集合里,当然升级的过程中,也要维持整数集合的有序性。流程如下:
升级编码为INTSET_ENC_INT32, 每个整数占4字节,并按照新的编码方式及元素个数扩容数组
倒序依次将数组中的元素拷贝到扩容后的正确位置
将待添加的元素放入数组末尾
最后,将inset结构体中的encoding属性改为INTSET_ENC_INT32,并更改length属性
升级具体过程
小林图解https://xiaolincoding.com/redis/data_struct/data_struct.html#%E6%95%B4%E6%95%B0%E9%9B%86%E5%90%88%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1
升级具体源码
插入一个数值在IntSet中
如超出当前编码则进行升级
插入时intsetSearch进行二分查找,确定插入元素是否存在,若不存在,即确定其插入位置,保证元素的有序性
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 */
// intset判空
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. */
// 首先判断值是否大于或者小于intset中的全部元素
// 如果满足任一条件 需要将pos设为0或者length 因为元素添加时也需要pos 因此需要这样判断
if (value > _intsetGet(is,max)) {
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;
}
}
// 找到时返回1 没找到时返回0 并且更新pos 表示value在intset中应该在的位置
if (value == cur) {
if (pos) *pos = mid;
return 1;
} else {
if (pos) *pos = min;
return 0;
}
}
小结
整数集合升级的好处是节省内存资源,但是整数集合不支持降级操作,一旦对数组进行了升级,就会一直保持升级后的状态。比如前面的升级操作的例子,如果删除了 65535 元素,整数集合的数组还是 int32_t 类型的,并不会因此降级为 int16_t 类型。、
IntSet特点
Redis会确保Intset中的元素唯一、有序
具备类型升级机制,可以节省内存空间
底层采用二分查找方式来查询