Redis数据结构 — IntSet

目录

整数集合IntSet结构设计

IntSet的升级操作

升级具体过程

升级具体源码

小结


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

整数集合IntSet结构设计

整数集合本质上是一块连续内存空间,它的结构定义如下:

typedef struct intset {
    //编码方式,控制实质数据开辟为多大
    uint32_t encoding;

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

    //声明一个保存元素的数组
    int8_t contents[];
} intset;

可以看到,保存元素的容器是一个 contents 数组,虽然 contents 被声明为 int8_t 类型的数组,但是实际上 contents 数组并不保存任何 int8_t 类型的元素,contents 数组的真正类型取决于 intset 结构体里的 encoding 属性的值。比如:

  • 如果 encoding 属性值为 INTSET_ENC_INT16,那么 contents 就是一个 int16_t 类型的数组,其中数组中每一个元素的类型都是 int16_t
  • 如果 encoding 属性值为 INTSET_ENC_INT32,那么 contents 就是一个 int32_t 类型的数组,其中数组中每一个元素的类型都是 int32_t
  • 如果 encoding 属性值为 INTSET_ENC_INT64,那么 contents 就是一个 int64_t 类型的数组,其中数组中每一个元素的类型都是 int64_t

 为了方便查找Redis源码中通过intsetSearch进行二分查找会将intset中所有的整数按照升序依次保存在contents数组中,结构如图: Redis数据结构 — IntSet_第1张图片

IntSet的升级操作

整数集合会有一个升级规则,就是当我们将一个新元素加入到整数集合里面,如果新元素的类型(int32_t)比整数集合现有所有元素的类型(int16_t)都要长时,整数集合需要先进行升级,也就是按新元素的类型(int32_t)扩展 contents 数组的空间大小,然后才能将新元素加入到整数集合里,当然升级的过程中,也要维持整数集合的有序性。流程如下:

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

  • 倒序依次将数组中的元素拷贝到扩容后的正确位置

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

  • 最后,将inset结构体中的encoding属性改为INTSET_ENC_INT32,并更改length属性

升级具体过程

小林图解https://xiaolincoding.com/redis/data_struct/data_struct.html#%E6%95%B4%E6%95%B0%E9%9B%86%E5%90%88%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1

Redis数据结构 — IntSet_第2张图片

升级具体源码

插入一个数值在IntSet中

Redis数据结构 — IntSet_第3张图片

如超出当前编码则进行升级

Redis数据结构 — IntSet_第4张图片

插入时intsetSearch进行二分查找,确定插入元素是否存在,若不存在,即确定其插入位置,保证元素的有序性

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;

    /* The value can never be found when the set is empty */
    // intset判空
    if (intrev32ifbe(is->length) == 0) {
        if (pos) *pos = 0;
        return 0;
    } else {
        /* Check for the case where we know we cannot find the value,
         * but do know the insert position. */
        // 首先判断值是否大于或者小于intset中的全部元素
        // 如果满足任一条件 需要将pos设为0或者length 因为元素添加时也需要pos 因此需要这样判断
        if (value > _intsetGet(is,max)) {
            if (pos) *pos = intrev32ifbe(is->length);
            return 0;
        } else if (value < _intsetGet(is,0)) {
            if (pos) *pos = 0;
            return 0;
        }
    }
	// 二分查找
    while(max >= min) {
        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;
        }
    }
	// 找到时返回1 没找到时返回0 并且更新pos 表示value在intset中应该在的位置
    if (value == cur) {
        if (pos) *pos = mid;
        return 1;
    } else {
        if (pos) *pos = min;
        return 0;
    }
}

小结

整数集合升级的好处节省内存资源,但是整数集合不支持降级操作,一旦对数组进行了升级,就会一直保持升级后的状态。比如前面的升级操作的例子,如果删除了 65535 元素,整数集合的数组还是 int32_t 类型的,并不会因此降级为 int16_t 类型。、

IntSet特点

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

  • 具备类型升级机制,可以节省内存空间

  • 底层采用二分查找方式来查询

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