保证没有重复;整数集合的结构体如下:
typedef struct intset {
uint32_t encoding; /* 编码方式 */
uint32_t length; /* 集合的元素数量 */
int8_t contents[]; /* 保存元素的数组 */
} intset;
按照大小升序排列,而且要求数组数字的唯一性,不允许重复。
length 记录了整数集合的元素个数,也就是contents[]数组的长度,所以长度获取复杂度为 O(1)。
contents[] 是一个柔性数组,虽然结构体数组声明的类型为 int8_t,但是存储的时候并不是 int8_t 类型,而是取决于 encoding 属性值,以下为各种属性值对应数字大小范围:
INTSET_ENC_INT16,那么 contents[] 数组的类型就是 int16_t,那么 数组的内存长度就是 sizeof(int16_t) * length,数组内数字大小范围为 -32 768 ~ 32 767
INTSET_ENC_INT32,那么 contents[] 数组的类型就是 int32_t,那么 数组的内存长度就是 sizeof(int32_t) * length,数组内数字大小范围为 -2 147 483 648 ~ 2 147 483 647
INTSET_ENC_INT64,那么 contents[] 数组的类型就是 int64_t,那么 数组的内存长度就是 sizeof(int64_t) * length,数组内数字大小范围为 -9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807
其中需要注意的是,intset可能会随着数据的添加而改变它的数据编码:
最开始,新创建的intset使用占内存最小的INTSET_ENC_INT16(值为2)作为数据编码。每添加一个新元素,则根据元素大小决定是否对数据编码进行升级。
在添加每个元素的过程中,intset始终保持从小到大有序。
与ziplist类似,intset也是按小端(little endian)模式存储的。
插入
intset *intsetAdd(intset *is, int64_t value, uint8_t *success)
获取新数值的类型_intsetValueEncoding
如果新加入的数值大于原编码类型大小,要升级编码类型intsetUpgradeAndAdd,把原来集合的每个元素取出来,再用新的编码重新写入新的位置
在集合中查找intsetSearch,如果已存在则终止操作,不存在则返回数值要保存的位置pos
为集合重新分配大小intsetResize,这个操作会引发内存realloc,可能带来一次数据拷贝
如果在集合中间插入数值,需要移动pos后边元素intsetMoveTail(memmove(dst,src,bytes),memmove保证了拷贝过程中不会造成数据重叠或覆盖)
插入新元素_intsetSet
集合元素个数+1
算法总的时间复杂度O(n)
注意:intsetAdd返回一个intset指针,调用方必须使用这个返回的intset,替换传进来的旧的intset
升级
static intset *intsetUpgradeAndAdd(intset *is, int64_t value)
判断新数值是否小于0,新数值肯定是绝对值最大得数,不是放表头就是放表尾
调整数组大小
从后往前遍历集合,把原来的值往升级后的位置移动
while(length--)
_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
根据数值的正负判断,将新元素插入表头或表尾
if (prepend)
_intsetSet(is,0,value);
else
_intsetSet(is,intrev32ifbe(is->length),value);
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
查找插入位置
static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos)
如果集合为空,返回pos = 0
如果value大于集合中最大值,则返回pos = is->length
如果value小于集合中最小值,则返回pos = 0, 否则二分法查找value插入位置:
while (max >= min) {
mid = (min + max) >> 1;
cur = _intsetGet(is, mid);
if (value > cur) {
min = mid + 1;
} else if (value < cur) {
max = mid - 1;
} else {
break;
}
}
如果找到了value,用pos存value的位置 pos = mid
如果没找到,则pos = value应该插入的位置 pos = min