Redis 数据结构之Intset 整数集合

整数集合(intset)用于有序、无重复地保存多个整数值, 根据元素的值, 自动选择该用什么长度的整数类型来保存元素。

举个例子, 如果在一个 intset 里面, 最长的元素可以用 int16_t 类型来保存, 那么这个 intset 的所有元素都以 int16_t 类型来保存。

另一方面, 如果有一个新元素要加入到这个 intset , 并且这个元素不能用 int16_t 类型来保存 —— 比如说, 新元素的长度为 int32_t , 那么这个 intset 就会自动进行“升级”: 先将集合中现有的所有元素从 int16_t 类型转换为 int32_t 类型, 接着再将新元素加入到集合中。

根据需要, intset 可以自动从 int16_t 升级到 int32_t 或 int64_t , 或者从 int32_t 升级到 int64_t 。

Intset 是集合键的底层实现之一,如果一个集合:只保存着整数元素且元素的数量不多。那么 Redis 就会使用 intset 来保存集合元素。

数据结构

以下是 intset.h/intset 类型的定义:

typedef struct intset {

    // 保存元素所使用的类型的长度
    uint32_t encoding;

    // 元素个数
    uint32_t length;

    // 保存元素的数组
    int8_t contents[];

} intset;

encoding 的值可以是以下三个常量之一(定义位于 intset.c ):

#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

contents 数组是实际保存元素的地方,数组中的元素有以下两个特性:

  • 元素不重复;
  • 元素在数组中由小到大排列;

contents 数组的 int8_t 类型声明比较容易让人误解,实际上, intset 并不使用 int8_t 类型来保存任何元素,结构中的这个类型声明只是作为一个占位符使用:在对 contents 中的元素进行读取或者写入时,程序并不是直接使用 contents 来对元素进行索引,而是根据 encoding 的值,对 contents 进行类型转换和指针运算,计算出元素在内存中的正确位置。在添加新元素,进行内存分配时,分配的空间也是由 encoding 的值决定。

API:
在这里插入图片描述

升级

添加新元素时,如果 intsetAdd 发现新元素,不能用现有的编码方式来保存,便会将升级集合和添加新元素的任务转交给 intsetUpgradeAndAdd 来完成,intsetUpgradeAndAdd 需要完成以下几个任务:

  • 对新元素进行检测,看保存这个新元素需要什么类型的编码;
  • 将集合 encoding 属性的值设置为新编码类型,并根据新编码类型,对整个 contents 数组进行内存重分配。
  • 调整 contents 数组内原有元素在内存中的排列方式,从旧编码调整为新编码。
  • 将新元素添加到集合中。
元素移动

在进行升级的过程中,需要对数组内的元素进行“类型转换”和“移动”操作。

其中, 移动不仅出现在升级(intsetUpgradeAndAdd)操作中, 还出现其他对 contents 数组内容进行增删的操作上, 比如 intsetAdd 和 intsetRemove , 因为这种移动操作需要处理 intset 中的所有元素, 所以这些函数的复杂度都不低于 O(N) 。

发布了202 篇原创文章 · 获赞 14 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/LU_ZHAO/article/details/105021768