IntSet结构
typedef struct intset {
uint32_t encoding; //编码方式 有三种编码方式,分别是int16、int32、int64
uint32_t length; //元素个数
int8_t contents[]; //实际存储数据的数组 指针
} intset;
encoding——编码方式
1、INTSET_ENC_INT16:
使用0来标识INTSET_ENC_INT16,占用2字节,16比特。存储范围为-32768 到 32767;
2、INTSET_ENC_INT32:
使用1来标识INTSET_ENC_INT32,占用4字节,32比特。存储范围为--2147483648 到 2147483647;
3、INTSET_ENC_INT64:
使用1来标识INTSET_ENC_INT64,占用8字节,64比特。
每个整数都将以对应编码方式的整数占用空间存储
编码方式为INTSET_ENC_INT32时空间占用图
IntSet有序性
intset 中的整数元素是有序的。虽然它是一个集合数据结构,不允许重复元素,但是元素在 intset 中是有序的。升序排序查找效率更高,范围查询效率更高。
编码升级
初始编码方式选择: 当创建一个新的 intset 时,它会初始选择一种编码方式。一般选择最小的能够容纳所有元素的编码方式,以节省内存。比如{1,2}选择INT16,{40000}选择INT32。
但会遇到一些问题,比如编码方式为int16的intset要新插入一个40000,新元素超出了当前编码方式的表示范围。
选择新的编码方式
1.确定新的编码方式,比如要插入40000编码方式升级为INTSET_ENC_INT32,新的编码方式每个整数占4个字节,数组中每个元素占位都应该改变保持一致。
2.对现有元素重新内存分配,并倒序插入扩容后的正确位置(正序插入会造成数据覆盖,内存地址重叠)。
3.然后再将要新插入的元素40000插入数组末尾,并更新length和encoding。
源码
插入一个新整数
/* 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. */
/* 如果整数值的编码方式大于当前 intset 的编码方式,需要进行升级 */
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. */
//如果该元素已经存在set中,设置success表示插入失败 注意这个方法,会更新&pos,寻找到该
//插入的位置
if (intsetSearch(is,value,&pos)) {
if (success) *success = 0;
return is;
}
/* 调整 intset 的大小,为新元素腾出位置 */
is = intsetResize(is,intrev32ifbe(is->length)+1);
if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
}
/* 在正确的位置插入新的整数值,并更新 intset 的长度 */
_intsetSet(is,pos,value);
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}
intsetSearch
这里会找到该插入的位置,并判断元素是不是已经存在,二分查找
/* Search for the position of an integer in the intset */
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;
/* 如果 intset 为空集合,那么无论如何都找不到值,返回插入位置 0 */
if (intrev32ifbe(is->length) == 0) {
if (pos) *pos = 0;
return 0;
} else {
/* 检查无法找到值的情况,但是知道插入位置 */
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;
}
}
// 判断是否找到值,更新插入位置
if (value == cur) {
if (pos) *pos = mid;
return 1; // 找到值
} else {
if (pos) *pos = min;
return 0; // 未找到值
}
}
编码升级逻辑
/* 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);
// 获取当前 intset 的长度
int length = intrev32ifbe(is->length);
// 判断是否需要在开头插入元素
int prepend = value < 0 ? 1 : 0;
/* 首先设置新的编码方式并进行扩容 */
is->encoding = intrev32ifbe(newenc);
is = intsetResize(is, intrev32ifbe(is->length) + 1);
/* 从后向前进行升级,以防覆盖值。
* 注意 "prepend" 变量用于确保在 intset 的开头或结尾有一个空白位置。 */
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);
return is;
}
INTSET只要有不重复元素插入就要扩容,保证了灵活性和内存利用,但也损失了一部分性能