无序集合
Redis 源码版本:Redis-6.0.9,本篇文章无序集合的代码均在
intset.h / intset.c
文件中。
Redis
通常使用字典结构保存用户集合数据,字典键存储集合元素,字典值为空。如果一个集合全是整数,则使用字典国语浪费内存。为此,Redis
设计了 intset
数据结构,专门用来保存整数集合数据。
定义
typedef struct intset {
uint32_t encoding; // 编码格式,intset 中所有元素必须是同一种编码格式
uint32_t length; // 集合中元素的数量
int8_t contents[]; // 存储元素数据,元素必须排序,并且无重复
} intset;
encoding
格式如下表所示:
定义 | 存储类型 |
---|---|
INTSET_ENC_INT16 |
int16_t |
INTSET_ENC_INT32 |
int32_t |
INTSET_ENC_INT64 |
int64_t |
intset
编码格式存在不同的级别。上表中编码格式的级别由低到高排序:
I N T S E T _ E N C _ I N T 16 < I N T S E T _ E N C _ I N T 32 < I N T S E T _ E N C _ I N T 64 \rm INTSET\_ENC\_INT16 \lt INTSET\_ENC\_INT32 \lt INTSET\_ENC\_INT64 INTSET_ENC_INT16<INTSET_ENC_INT32<INTSET_ENC_INT64
intsetAdd
- 函数功能:向
intset
中插入一个元素。 - 参数:
intset *is
:插入到指定的intset
中。int64_t value
:插入指定的元素。uint8_t *success
:是否插入成功。
- 返回值: 返回该
intset
,因为在该函数中可能会修改原intset
的地址。
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
uint8_t valenc = _intsetValueEncoding(value); // 判断 val 值对应的 intset 编码
uint32_t pos;
if (success) *success = 1; // 如果 success != NULL 默认 Add 成功
/* 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. */
// 如果参数 value 对应的编码大于 intset 中元素的编码。 intrev32ifbe 函数用于转换字节序
if (valenc > intrev32ifbe(is->encoding)) {
/* This always succeeds, so we don't need to curry *success. */
return intsetUpgradeAndAdd(is,value); // 升级 intset 的编码,然后插入数据
} 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)) {
// 如果 intset 中已经存在与 value 相同的值
if (success) *success = 0; // 那么插入失败
return is;
}
// 走到这里说明 intset 中不存在与 value 相同的值,并且不用调整原 intset 的编码
is = intsetResize(is,intrev32ifbe(is->length)+1); // 只需要调整 intset 的大小就行啦
// 这个 if 判断几乎不可能判断失败的,但是为什么这么做,懂得都懂
if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1); // 将原 intset pos 位置及其之后的元素全部后移一个下标
}
_intsetSet(is,pos,value); // 将 value 插入到 pos 位置处
is->length = intrev32ifbe(intrev32ifbe(is->length)+1); // 更新 intset 的 length 属性
return is;
}
_intsetValueEncoding
- 函数功能:根据整数的大小判断其在
intset
中的编码。 - 参数:
int64_t v)
:待判断编码的整数。
- 返回值:该整数的编码。
#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;
}
intsetUpgradeAndAdd
- 函数功能:升级
intset
的编码格式,并将指定的value
值插入到intset
。 - 参数:
intset *is
:需要升级编码和插入元素的intset
。int64_t value
:待插入的value
。
- 返回值:升级之后的
intset
。
/* 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); // 当前 intset 的编码格式
uint8_t newenc = _intsetValueEncoding(value); // 升级之后 intset 的编码格式
int length = intrev32ifbe(is->length); // 当前 intset 中元素的个数
int prepend = value < 0 ? 1 : 0; // 判断下标的偏移
/* First set new encoding and resize */
is->encoding = intrev32ifbe(newenc); // 修改 intset 的编码
is = intsetResize(is,intrev32ifbe(is->length)+1); // 调整 intset 的大小,+1 是因为等会儿要插入一个新元素嘛
// _intsetGetEncoded(is,length,curenc) 将原来的 intset 的数据获取出来,从后向前获取的哇
// _intsetSet 将 _intsetGetEncoded 直接插入到新的 intset 中,从后向前插入
// length+prepend 如果 value 小于 0 prepend 就是 1,因为负数是插入到柔性数组下标为 0 的位置,下标在数值上就会向后偏移一个。
// 如果 value >= 0 prepend 就是 0 直接插入到柔性数组末尾就行了,下标在数值上就不会偏移了
while(length--)
_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
/* Set the value at the beginning or the end. */
// [1](见注解1)
if (prepend)
_intsetSet(is,0,value); // 如果 value < 0 将新插入的值设置到柔性数组下标为 0 的位置
else
_intsetSet(is,intrev32ifbe(is->length),value); // 如果 value >= 0 将新插入的值设置到柔性数组末尾哈
is->length = intrev32ifbe(intrev32ifbe(is->length)+1); // 更新 intset 的 length 属性 (集合中元素的个数)
return is;
}
-
intset
虽说是用来实现无序集合的,但是其内部数据在柔性数组中的排列