1 数据结构
(1)content是已经排序
(2)是数组类型,紧凑
(3)encoding描述16字节、32字节、64字节
typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;
ecoding的取值空间,不但有逻辑含义,标记类型,还有实际含义:16 表示2字节,32表示4字节,64表示8字节。
/* Note that these encodings are ordered, so:
* INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64. */
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
2 方法
返回指定编码,指定位置的值。 pos是在contents[]数组中的偏移量,64位每次移动8字节,32为每次移动4字节,16位每次移动2字节。如果是大端模式,需要转换成小端模式,原因稍后说到。
set方法,更根据encoding在指定的位置设置int值。步骤是先set value到指定位置,然后转成小端模式。
/* Return the value at pos, given an encoding. */
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;
}
}
set方法,更根据encoding在指定的位置设置int值。步骤是先set value到指定位置,然后转成小端模式。
/* Set the value at pos, using the configured encoding. */
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);
}
}
新建
/* Create an empty intset. */
intset *intsetNew(void) {
intset *is = zmalloc(sizeof(intset)); // zmalloc 前面是长度PREFIX_SIZE , 后面是PADDING, 凑够sizeof(long) 内存对齐
is->encoding = intrev32ifbe(INTSET_ENC_INT16); // 初始为16为整形
is->length = 0;
return is;
}
resize
/* Resize the intset */
static intset *intsetResize(intset *is, uint32_t len) {
uint32_t size = len*intrev32ifbe(is->encoding); // encoding 就是一个元素的长度,参数len是数量,(预留一些空间,比inset结构原有元素要多一点)
is = zrealloc(is,sizeof(intset)+size); //
return is;
}
查找
/* Search for the position of "value". Return 1 when the value was found and
* sets "pos" to the position of the value within the intset. Return 0 when
* the value is not present in the intset and sets "pos" to the position
* where "value" can be inserted. */
/**
检索value的位置,采用二分查找算法。
返回值1表示value被查找到,设置pos 等于值所在的位置;
返回值0表示没有找到value,pos是可以插入的位置。
*/
static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
// 数组长度使用4字节整型
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 */
// 长度为0,直接返回
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. */
// 大于最大的整型
if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {
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;
}
}
// 查到了,设置value的位置
if (value == cur) {
if (pos) *pos = mid;
return 1;
} else {
// 没查到
if (pos) *pos = min;
return 0;
}
}
insetUpdateAndAdd升级且添加
/* Upgrades the intset to a larger encoding and inserts the given integer. */
/**
提升编码并且添加一个值。16-->32-->64位编码
*/
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
uint8_t curenc = intrev32ifbe(is->encoding);
uint8_t newenc = _intsetValueEncoding(value);
int length = intrev32ifbe(is->length);
// prepend做什么用的?
int prepend = value < 0 ? 1 : 0;
/* First set new encoding and resize */
// 设置新的encoding
is->encoding = intrev32ifbe(newenc);
// resize通过realloc方法重新分配内存,数据内容会拷贝到新内存
// 为什么多了一个单位?
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. */
/**
从后向前reset,不会覆盖掉数据,(因为新申请了一份内存空间,原来的低encoding的数都在最前面)
*/
while(length--)
// 按照旧的encoding从intset(is)里面获取数值;is里面的encoding已经在resize前设置成新的
// 如果新增数据 <0,最前面会空一格位置 prepend, 为什么?
_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
/* Set the value at the beginning or the end. */
/** 源码证明,这个value只是个占位符,不是实际要插入的数据;必须必最大的大,或者比最小的小;
如果比最小的小,则放在最前面;否则放在最后面
*/
if (prepend)
_intsetSet(is,0,value);
else
_intsetSet(is,intrev32ifbe(is->length),value);
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}
/**
向后搬一段,这一段有多长;从from开始到数组结束;is->length - from
*/
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);
}
// 有别于memcpy函数,如果目标和源有重叠;不会丢数据
memmove(dst,src,bytes);
}
memcpy和memmove()都是C语言中的库函数,在头文件string.h中,作用是拷贝一定长度的内存的内容,原型分别如下:
void *memcpy(void *dst, const void *src, size_t count);
void *memmove(void *dst, const void *src, size_t count);
他们的作用是一样的,唯一的区别是,当内存发生局部重叠的时候,memmove保证拷贝的结果是正确的,memcpy不保证拷贝的结果的正确。

第一种情况下,拷贝重叠的区域不会出现问题,内容均可以正确的被拷贝。
第二种情况下,问题出现在 右边的两个字节,这两个字节的原来的内容首先就被覆盖了 , 而且没有保存。所以接下来拷贝的时候,拷贝的是已经被覆盖的内容 ,显然这是有问题的。
增加insetAdd方法
/* Insert an integer in the intset */
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
// 获取值的encoding
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. */
// 如果添加值的encoding大于inset中原来的encoding则扩容
// 为什么大于0就append,小于0就prepend呢???
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. */
// 如果添加的值已经存在,直接返回
if (intsetSearch(is,value,&pos)) {
if (success) *success = 0;
return is;
}
// 如果添加值不存在,先扩容 + 1;然后pos后面的数组往后平移一个单位
is = intsetResize(is,intrev32ifbe(is->length)+1);
if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
}
// 设置新增的值
_intsetSet(is,pos,value);
// 更新length
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}
删除
/* Delete integer from intset */
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 */
// 如果不是最后一个,pos后面的统统往前挪一个位置
if (pos < (len-1)) intsetMoveTail(is,pos+1,pos);
is = intsetResize(is,len-1);
// realloc方法,如果空间够,继续使用原来的内存;返回新的指针指向老内存,不需要free原来的指针
is->length = intrev32ifbe(len-1);
}
return is;
}
参考 http://blog.chinaunix.net/uid-26495963-id-3080058.html