Redis(四)整数集合介绍及源码解析

一.概述

       整数集合(intset)与hash表,跳跃表等一样是集合键的底层实现之一,一般用于保存数量不多的整数,这是由于其实现机制导致的不能存储过多元素,否则会造成效率问题。集合键的内部结构可以使用Redis命令OBJECT ENCODING获取

二.整数集合结构

       整数集合可以用于保存int16_t,int32_t或int64_t的整数值,并且从小到大排序且保证不会出现重复元素。在Redis中的数据结构如下:

typedef struct inset {
    // 编码方式,即保存的值类型(int16_t, int32_t, int64_t)
    uint32_t encoding;

    // 集合包含的元素数量
    uint32_t length;

    // 保存元素的数值
    // 【注】:其实就相当于一个字节数组,再根据编码方式进行存储与读取(c++可以重新解读一块内存)
    int8_t contents[]
}intset;

三.升级

       假如整数集合中起初存储的都是int16_t类型的元素,而此时要添加一个int32_t类型的元素,那么将进行集合升级,首先进行集合内存的扩展,以便足以容纳新元素与升级后的旧元素(旧元素会被转化为int32_t类型存储)。之后从末至尾将元素进行提升并后移。但不支持降级操作。优点如下:

  • 提升灵活性,同一块内存可同时保存int64_t,int32_t和int16_t类型的元素。
  • 节约内存,若不能升级内存,想在同一块内存中保存3种元素,则必须以最大的类型来开辟区间。

四.部分源码

1.根据值判断需要采用的编码方式及获取第pos个值

#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;
}


// 根据编码方式,获取整数集合中第pos个值
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) {
        // ((int64_t*)is->contents)+pos 指向第pos个元素的起始位置
        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;
    }
}

2.在整数集合中搜索值

    由于整数集合是有序集合,因此进行折半查找,此处不予赘述。

3.在整数集合中添加数据(提升编码)

    当向整数集合中添加编码时,若当前编码大小不可容纳,则需要提升编码方式。

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)) {
        /* This always succeeds, so we don't need to curry *success. */
        return intsetUpgradeAndAdd(is,value);
    } else {
        // 查找该值是否已存在(整数集合无重复元素),若不存在则pos返回的是插入位置
        if (intsetSearch(is,value,&pos)) {
            if (success) *success = 0;
            return is;
        }

        is = intsetResize(is,intrev32ifbe(is->length)+1); // 若不提升则直接扩展空间并插入元素
        if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1); // 将pos位起的元素都后移一位
    }

    _intsetSet(is,pos,value); // 将value放至第pos位
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1); // 更新整数集合长度
    return is;
}

// 添加新值并提升编码类型
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;

    // 设置新的编码类型
    is->encoding = intrev32ifbe(newenc);
    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. */
    while(length--)
        _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));

    /* Set the value at the beginning or the end. */
    if (prepend)
        _intsetSet(is,0,value);
    else
        _intsetSet(is,intrev32ifbe(is->length),value);
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值