Redis 基础数据结构
redis基础数据结构:动态字符串SDS、intSet、Dict、ZipList、QuickList、SkipList、RedisObject。
数据结构——IntSet
IntSet是redis集合的一种实现方式,基于整数数组实现。具备长度可变、有序等特征。
typedef struct intset {
uint32_t encoding; // 编码方式,支持存放16位,32位和64位的整数
uint32_t length; // 元素个数
int8_t contents[]; //整数数组,保持集合数据。有序。该元素仅是有符号8bit, 所有是保持指向数组的第一个元素。
} intset;
- uint32_t encoding:
编码方式,支持存放16位,32位和64位的整数。编码如下:/*INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64. */ #define INTSET_ENC_INT16 (sizeof(int16_t))//类似short类型,2字节 #define INTSET_ENC_INT32 (sizeof(int32_t))//类似int类型,4字节 #define INTSET_ENC_INT64 (sizeof(int64_t))//类似long类型,8字节
- uint32_t length:
元素个数 - int8_t contents[]:
整数数组,保存集合数据。有序。
该元素仅是有符号8bit, 所以是保存指向数组的第一个元素。
IntSet 升级
若,目前intset A = {1,3,2}, 采用的是INTSET_ENC_INT16编码。 插入一个新数据60000,则超出有符号16bit,需要升级。
以上述流程来说: 添加60000,这个数字超出int16_t的范围,intset会自动升级到合适的大小。上图则是扩容前后数组的状态。
步骤如下:
-
升级编码为INTSET_ENC_INT32,每个整数占4个字节,并按照新的编码方式及元素个数扩容数组
-
倒序依次将数组中的元素拷贝到正确位置。(由于编码升级,占用字节由小变大,若正序向后拷贝,如
intset A[0]=1
所占2字节,扩容成4字节,会将原先A[1]=10
的数据覆盖,所以,需要倒序拷贝) -
将新增的元素放入数组首或尾(因为是有符号的,负数大在首,正数大在尾)
-
最后,将编码encoding改为INTSET_ENC_INT32, length改为4
IntSet 添加元素——源码分析
1、首先先查看定义于intset.h中intset *intsetAdd(intset *is, int64_t value, uint8_t *success);
/* 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;
// 1、如果新增数据的编码> 原有encoding,需要升级后添加数据
if (valenc > intrev32ifbe(is->encoding)) {
/* This always succeeds, so we don't need to curry *success. */
return intsetUpgradeAndAdd(is,value);
} else {
// 2、如果不需要升级,先二分查找数据是否存在,若不存在,并返回需要插入数据的pos位置
// 2.1 若数据存在,则返回false
if (intsetSearch(is,value,&pos)) {
if (success) *success = 0;
return is;
}
// 2.2 若数据不存在,重新申请intset内存为length+1
is = intsetResize(is,intrev32ifbe(is->length)+1);
// 3、若pos位置小于intset大小,则将pos位置的元素后移一位
if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
}
// 4、将value插入指定pos位置
_intsetSet(is,pos,value);
// 5、将intset的length设置为length+1
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}
新增数据步骤如下:
- 首先获取新数据的编码:
_intsetValueEncoding(value)
- 如果新增数据的编码 大于原有encoding,需要升级intset后添加数据
- 如果不需要升级,先二分查找数据是否存在,若不存在,并返回需要插入数据的pos位置
- 将新增数据value插入指定pos位置
- 将intset的length设置为length+1
2、紧接着进入升级intset方法intsetUpgradeAndAdd
/* 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);
int length = intrev32ifbe(is->length);
int prepend = value < 0 ? 1 : 0;
// 1、设置新的编码
is->encoding = intrev32ifbe(newenc);
// 2、重新申请数组内存大小, 调整后的大小= (length+1) * encoding, 加1是添加新数据的内存
is = intsetResize(is,intrev32ifbe(is->length)+1);
// 3、倒序(从后向前)升级,以至于不会覆盖原有数据
while(length--) // 首先将length--;改为和下标保持一致
//3.1 在指定length+prepend位置,插入新元素。即:倒序将原先数组的每一个元素copy到正确的位置
_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
//4、若value<0,负数,则在数组头部添加;若value>0,整数,则在数组尾部添加
//(因为都需要升级编码,那么新增数据的绝对值一定大于原先数组的任意元素,所以,一定是首或尾插入,只需判断正负数即可判断插入位置)
if (prepend)
_intsetSet(is,0,value);
else
_intsetSet(is,intrev32ifbe(is->length),value);
// 5、长度改为length+1
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}
升级intset步骤如下:
- 设置新的编码
- 重新申请数组内存大小, 调整后的大小= (length+1) * encoding, 加1是添加新数据的内存
- 倒序(从后向前)升级,以至于不会覆盖原有数据
- 若新增数据value<0,则在数组头部添加;若value>0,则在数组尾部添加
- intset长度改为length+1
3、最后进入查找数据方法intsetSearch
——插入时同时保证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;
// 1、如果intset为空,返回pos=0
if (intrev32ifbe(is->length) == 0) {
if (pos) *pos = 0;
return 0;
} else {
// 2、如果value>max,返回pos=length
if (value > _intsetGet(is,max)) {
if (pos) *pos = intrev32ifbe(is->length);
return 0;
// 3、如果value<min,返回pos=0
} else if (value < _intsetGet(is,0)) {
if (pos) *pos = 0;
return 0;
}
}
// 4、二分查找value应该在的位置pos
while(max >= min) {
// >> 1 = 右移1位,等于/2,即二分查找
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;
}
}
// 5、若value == cur, 则代表数据存在,返回1
if (value == cur) {
if (pos) *pos = mid;
return 1;
} else {
// 5、若value != cur, 则代表数据不存在,返回pos=min
if (pos) *pos = min;
return 0;
}
}
上述方法是典型的二分查找,笔者在此就不做赘述。
注意: intsetSearch
是保证intset有序和唯一的逻辑