IntSet

基本概述

IntSet是Redis中set集合的一种实现方式,基于整数数组来实现,并且具备长度可变、有序等特征。

结构如下:

typedef struct intset {
    uint32_t encoding; /* 编码方式,支持存放16位、32位、64位整数(4字节32比特位)*/ 
    uint32_t length; /* 元素个数 */
    int8_t contents[]; /* 整数数组,保存集合数据*/
} intset;

其中的encoding包含三种模式,表示存储的整数大小不同:

/* Note that these encodings are ordered, so:
 * INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64. */
#define INTSET_ENC_INT16 (sizeof(int16_t)) /* 2字节整数,范围类似java的short*/
#define INTSET_ENC_INT32 (sizeof(int32_t)) /* 4字节整数,范围类似java的int */
#define INTSET_ENC_INT64 (sizeof(int64_t)) /* 8字节整数,范围类似java的long */

为了方便查找,Redis会将intset中所有的整数按照升序依次保存在contents数组中,结构如图:(数组中元素大小要固定,方便寻址、快速定位元素

IntSet_第1张图片

IntSet_第2张图片

上述,数组中每个数字都在int16_t的范围内,因此采用的编码方式是INTSET_ENC_INT16,每部分占用的字节大小

  • encoding:4字节
  • length:4字节
  • contents:2字节 * 3 = 6字节

IntSet升级

现在,假设有一个intset,元素为{5,10,20},采用的编码是INTSET_ENC_INT16,则每个整数占2字节:

image-20230612233307741

此时,向该其中添加一个数字:50000,这个数字超出了int16_t的范围,intset会自动升级编码方式到合适的大小。

升级流程:

升级编码为INTSET_ENC_INT32, 每个整数占4字节,并按照新的编码方式及元素个数扩容数组

倒序依次将数组中的元素拷贝到扩容后的正确位置(元素下标以及元素所占字节,倒序防止覆盖原有数据

③ 将待添加的元素放入数组末尾

④ 最后,将inset的encoding属性改为INTSET_ENC_INT32,将length属性改为4

image-20230612233632386

IntSet新增流程

源码如下:

intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
    // 获取新增值的编码
    uint8_t valenc = _intsetValueEncoding(value);
    uint32_t pos;
    if (success) *success = 1;

    // 判断编码是不是超过了当前intset的编码
    if (valenc > intrev32ifbe(is->encoding)) {
        // 超出编码,需要升级
        return intsetUpgradeAndAdd(is,value);
    } else {
        // 在当前intset中查找值与value一样的元素的角标pos
        if (intsetSearch(is,value,&pos)) { // 二分查找
            //如果找到了,则无需插入,直接结束并返回失败(set)
            if (success) *success = 0;
            return is;
        }

        // 数组扩容
        is = intsetResize(is,intrev32ifbe(is->length)+1);
        // 移动数组中pos之后的元素到pos+1,给新元素腾出空间
        if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
    }

    // 插入新元素
    _intsetSet(is,pos,value);
    // 重置元素长度
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
    // 获取当前intset编码
    uint8_t curenc = intrev32ifbe(is->encoding);
    // 获取新增值编码
    uint8_t newenc = _intsetValueEncoding(value);
    // 获取元素个数
    int length = intrev32ifbe(is->length);
    // 判断新元素是大于0还是小于0 ,小于0插入队首、大于0插入队尾
    int prepend = value < 0 ? 1 : 0;

    // 重置编码为新编码
    is->encoding = intrev32ifbe(newenc);
    // 重置数组大小
    is = intsetResize(is,intrev32ifbe(is->length)+1);

    // 倒序遍历,逐个搬运元素到新的位置,_intsetGetEncoded按照旧编码方式查找旧元素
    while(length--)
        _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));

    // value 值大于0,新元素放在末尾;value值小于0,最前面空出一个位置,插入队首(判断元素插入队首还是队尾)
    if (prepend)
        _intsetSet(is,0,value);
    else
        _intsetSet(is,intrev32ifbe(is->length),value);
    // 修改数组长度
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}

优点

Intset可以看做是特殊的整数数组,具备一些特点:

① Redis会确保Intset中的元素唯一、有序

② 具备类型(整数编码类型)升级机制,可以节省内存空间

③ 底层采用二分查找方式来查询(少量数据比较合适,数据量大了,二分查找会影响性能)

你可能感兴趣的:(#,redis,redis,数据结构)