redis 源码阅读 - 内存编码篇 - 有序整数集合 intset
目录
一、阅读内存编码
1.有序整数集合 intset.h 和 intset.c
数据结构定义
-
有序整数集合
typedef struct intset { // 编码方式 uint32_t encoding; // 元素数量 uint32_t length; // 保存整数元素的数组 int8_t contents[]; } intset;
-
编码方式
#define INTSET_ENC_INT16 (sizeof(int16_t)) #define INTSET_ENC_INT32 (sizeof(int32_t)) #define INTSET_ENC_INT64 (sizeof(int64_t)) static uint8_t _intsetValueEncoding(int64_t v) { if (v < INT32_MIN || v > INT32_MAX) return INTSET_ENC_INT64; else if (v < INT16_MIN || v > INT16_MAX) return INTSET_ENC_INT32; else return INTSET_ENC_INT16; }
Helper函数(可跳过,需要时阅读)
-
set O(1)
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); } }
-
get O(1)
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; } } static int64_t _intsetGet(intset *is, int pos) { return _intsetGetEncoded(is,pos,intrev32ifbe(is->encoding)); }
-
resize
static intset *intsetResize(intset *is, uint32_t len) { // 计算数组的空间大小 uint32_t size = len*intrev32ifbe(is->encoding); // zrealloc,如果比原来要大,那么数组原有的数据会被保留 is = zrealloc(is,sizeof(intset)+size); return is; }
构造函数
intset *intsetNew(void) {
// 不分配元素空间
intset *is = zmalloc(sizeof(intset));
// 默认16编码
is->encoding = intrev32ifbe(INTSET_ENC_INT16);
// 初始化元素数量
is->length = 0;
return is;
}
add value to set
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);
// 如果元素不是添加到队尾
// 向后迁移后面的元素
// 举个例子
// 如果数组为:
// | x | y | z | ? |
// 而新元素 n 的 pos 为 1 ,那么数组将移动 y 和 z 两个元素
// | x | y | y | z |
// 这样就可以将新元素设置到 pos 上了:
// | x | n | y | z |
// T = O(N)
if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
}
// 设置新值
_intsetSet(is,pos,value);
// 更新长度
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}
get value by index
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value) {
// 长度范围内
if (pos < intrev32ifbe(is->length)) {
// 保存值到指针
*value = _intsetGet(is,pos);
return 1;
}
return 0;
}
int64_t intsetRandom(intset *is) {
return _intsetGet(is,rand()%intrev32ifbe(is->length));
}
get index by value
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;
// is 为空
if (intrev32ifbe(is->length) == 0) {
if (pos) *pos = 0;
return 0;
} else {
// value 比最后一个值都要大, 则不存在
// pos 指向最后一个之后,但不扩容
if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {
if (pos) *pos = intrev32ifbe(is->length);
return 0;
// value 比最前一个值都要小,则不存在
// pos指向第一个,但不扩容
} else if (value < _intsetGet(is,0)) {
if (pos) *pos = 0;
return 0;
}
}
// 二分查找 T = O(log N)
while(max >= min) {
mid = (min+max)/2;
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;
}
}
uint8_t intsetFind(intset *is, int64_t value) {
// 计算编码
uint8_t valenc = _intsetValueEncoding(value);
return valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,NULL);
}
upgrade encoding
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
// 旧编码方式
uint8_t curenc = intrev32ifbe(is->encoding);
// 新编码方式
uint8_t newenc = _intsetValueEncoding(value);
// 当前集合的元素数量
int length = intrev32ifbe(is->length);
// |value| 一定在集合中最大,大于0最后面,小于0最前面
int prepend = value < 0 ? 1 : 0;
// 更新集合的编码方式
is->encoding = intrev32ifbe(newenc);
// 内存申请
is = intsetResize(is,intrev32ifbe(is->length)+1);
// 按旧编码拿出元素,按新编码放入位置
// 处理完所有旧元素即完成,prepend控制空白位置留在哪
// 举个例子,假设原来有 curenc 编码的三个元素,它们在数组中排列如下:
// | x | y | z |
// 当程序对数组进行重分配之后,数组就被扩容了(符号 ? 表示未使用的内存):
// | x | y | z | ? | ? | ? |
// 这时程序从数组后端开始,重新插入元素:
// | x | y | z | ? | z | ? |
// | x | y | y | z | ? |
// | x | y | z | ? |
// 最后,程序可以将新元素添加到最后 ? 号标示的位置中:
// | x | y | z | new |
// 上面演示的是新元素比原来的所有元素都大的情况,也即是 prepend == 0
// 当新元素比原来的所有元素都小时(prepend == 1),调整的过程如下:
// | x | y | z | ? | ? | ? |
// | x | y | z | ? | ? | z |
// | x | y | z | ? | y | z |
// | x | y | x | y | z |
// 当添加新值时,原本的 | x | y | 的数据将被新值代替
// | new | x | y | z |
// T = O(N)
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;
}
move back or forward
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);
// 根据不同的编码
// src = (Enc_t*)is->contents+from 记录移动开始的位置
// dst = (Enc_t*)is_.contents+to 记录移动结束的位置
// bytes *= sizeof(Enc_t) 计算一共要移动多少字节
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);
}
remove
intset *intsetRemove(intset *is, int64_t value, int *success) {
// 计算 value 的编码方式
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);
// 设置删除成功
if (success) *success = 1;
// 前移一位
if (pos < (len-1)) intsetMoveTail(is,pos+1,pos);
// 缩小大小
is = intsetResize(is,len-1);
// 更新数量
is->length = intrev32ifbe(len-1);
}
return is;
}