Redis inset内部结构

Redis中的Intset结构体用于存储整数集合,适用于只保存整数且数量不多的情况。它是一个有序、无重复的整数数组,支持不同大小的整数编码。在添加新元素时,如果需要不同编码,Intset会进行升级操作,包括改变编码类型、内存重分配和元素转换。升级过程中,原有元素会按新编码重新排列,确保内存中有序。Intset的搜索和插入操作也遵循其内部结构特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ntset 和 dict 都是 sadd 命令的底层数据结构,当添加的所有数据都是整数时,会使用前者;否则使用后者。特别的,当遇到添加数据为字符串,即不能表示为整数时,redis 会把数据结构转换为 dict,即把 intset 中的数据全部搬迁到 dict。

整数集合的应用

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

intset 结构体

intset 底层本质是一个有序的、不重复的、整型的数组,支持不同类型整数

  1: typedef struct intset {
  2:     // 每个整数的类型
  3:     uint32_t encoding;
  4:  
  5:     // intset 长度
  6:     uint32_t length;
  7:  
  8:     // 整数数组
  9:     int8_t contents[];
 10: } intset;

encoding 的值可以是以下三个常量的其中一个 :
# 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 的值决定。

inset操作

升级

在添加新元素时,如果 intsetAdd 发现新元素不能用现有的编码方式来保存,它就会将升级集合和添加新元素的任务转交给 intsetUpgradeAndAdd 来完成

intsetUpgradeAndAdd 需要完成以下几个任务:
1. 对新元素进行检测,看保存这个新元素需要什么类型的编码;
2. 将集合 encoding 属性的值设置为新编码类型,并根据新编码类型,对整个 contents 数
组进行内存重分配。
3. 调整 contents 数组内原有元素在内存中的排列方式,让它们从旧编码调整为新编码。
4. 将新元素添加到集合中。
整个过程中,最复杂的就是第三步,让我们用一个例子来理解这个步

 

假设有一个 intset ,里面包含三个用 int16_t 方式保存的数值,分别是 1 、2 和 3 ,它的结
构如下:
intset->encoding = INTSET_ENC_INT16;
intset->length = 3;
intset->contents = [1, 2, 3];
其中,intset->contents 在内存中的排列如下:
image

现在,我们要要将一个长度为 int32_t 的值 65535 加入到集合中,intset 需要执行以下步骤:
1. 将 encoding 属性设置为 INTSET_ENC_INT32 。
2. 根据 encoding 属性的值,对 contents 数组进行内存重分配。
重分配完成之后,contents 在内存中的排列如下:

image

contents 数组现在共有可容纳 4 个 int32_t 值的空间。
3. 因为原来的 3 个 int16_t 值还“挤在”contents 前面的 48 个位里,所以程序需要对它们
进行移动和类型转换,从而让它们适应集合的新编码方式。
首先是移动 3

image

4. 最后,将新值 65535 添加到数组:

intset 搜索

  1: static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
  2:     int min = 0, max = intrev32ifbe(is->length)-1, mid = -1;
  3:     int64_t cur = -1;
  4:  
  5:     /* The value can never be found when the set is empty */
  6:     // 集合为空
  7:     if (intrev32ifbe(is->length) == 0) {
  8:         if (pos) *pos = 0;
  9:         return 0;
 10:     } else {
 11:         /* Check for the case where we know we cannot find the value,
 12:          * but do know the insert position. */
 13:         // value 比最大元素还大
 14:         if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {
 15:             if (pos) *pos = intrev32ifbe(is->length);
 16:             return 0;
 17:         // value 比最小元素还小
 18:         } else if (value < _intsetGet(is,0)) {
 19:             if (pos) *pos = 0;
 20:             return 0;
 21:         }
 22:     }
 23:  
 24:     // 二分查找
 25:     while(max >= min) {
 26:         mid = (min+max)/2;
 27:         cur = _intsetGet(is,mid);
 28:         if (value > cur) {
 29:             min = mid+1;
 30:         } else if (value < cur) {
 31:             max = mid-1;
 32:         } else {
 33:             break;
 34:         }
 35:     }
 36:  
 37:     if (value == cur) {
 38:         if (pos) *pos = mid;
 39:         return 1;
 40:     } else {
 41:         if (pos) *pos = min;
 42:         return 0;
 43:     }
 44: }

intset 插入

  1: /* Insert an integer in the intset */
  2: intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
  3:     uint8_t valenc = _intsetValueEncoding(value);
  4:     uint32_t pos;
  5:     if (success) *success = 1;
  6:  
  7:     /* Upgrade encoding if necessary. If we need to upgrade, we know that
  8:      * this value should be either appended (if > 0) or prepended (if < 0),
  9:      * because it lies outside the range of existing values. */
 10:     // 需要插入整数的所需内存超出了原有集合整数的范围,即内存类型不同,
 11:     // 则升级整数类型
 12:     if (valenc > intrev32ifbe(is->encoding)) {
 13:         /* This always succeeds, so we don't need to curry *success. */
 14:         return intsetUpgradeAndAdd(is,value);
 15:  
 16:     // 正常,分配内存,插入
 17:     } else {
 18:         // intset 内部不允许重复
 19:         /* Abort if the value is already present in the set.
 20:          * This call will populate "pos" with the right position to insert
 21:          * the value when it cannot be found. */
 22:         if (intsetSearch(is,value,&pos)) {
 23:             if (success) *success = 0;
 24:             return is;
 25:         }
 26:  
 27:         // realloc
 28:         is = intsetResize(is,intrev32ifbe(is->length)+1);
 29:  
 30:         // 迁移内存,腾出空间给新的数据。intsetMoveTail() 完成内存迁移工作
 31:         if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
 32:     }
 33:  
 34:     // 在腾出的空间中设置新的数据
 35:     _intsetSet(is,pos,value);
 36:  
 37:     // 更新 intset size
 38:     is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
 39:     return is;
 40: }
 41:  
 42: // 升级整数类型,譬如从 short->int。当插入数据的内存占用比原有数据大
 43: // 的时候,会被调用
 44: /* Upgrades the intset to a larger encoding and inserts the given integer. */
 45: static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
 46:     uint8_t curenc = intrev32ifbe(is->encoding);
 47:     uint8_t newenc = _intsetValueEncoding(value);
 48:     int length = intrev32ifbe(is->length);
 49:  
 50:     // value<0 头插,value>0 尾插
 51:     int prepend = value < 0 ? 1 : 0;
 52:  
 53:     // realloc
 54:     /* First set new encoding and resize */
 55:     is->encoding = intrev32ifbe(newenc);
 56:     is = intsetResize(is,intrev32ifbe(is->length)+1);
 57:  
 58:     // 逆向处理,防止数据被覆盖,一般的插入排序步骤
 59:     /* Upgrade back-to-front so we don't overwrite values.
 60:      * Note that the "prepend" variable is used to make sure we have an empty
 61:      * space at either the beginning or the end of the intset. */
 62:     while(length--)
 63:         _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
 64:  
 65:     // value<0 放在集合开头,否则放在集合末尾。
 66:     // 因为,此函数是对整数所占内存进行升级,意味着 value 不是在集合中最大就是最小!
 67:     /* Set the value at the beginning or the end. */
 68:     if (prepend)
 69:         _intsetSet(is,0,value);
 70:     else
 71:         _intsetSet(is,intrev32ifbe(is->length),value);
 72:  
 73:     // 更新 set size
 74:     is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
 75:     return is;
 76: }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值