Redis 源码学习记录:集合 (set)

无序集合

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;
}
  1. intset 虽说是用来实现无序集合的,但是其内部数据在柔性数组中的排列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姬如祎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值