首页 > 数据库 >Redis的设计与实现(5)-整数集合

Redis的设计与实现(5)-整数集合

时间:2023-02-02 13:33:26浏览次数:56  
标签:元素 Redis 整数 类型 数组 集合 新元素

整数集合(intset)是集合键的底层实现之一: 当一个集合只包含整数值元素, 并且这个集合的元素数量不多时, Redis 就会使用整数集合作为集合键的底层实现.

整数集合 (intset) 是 Redis 用于保存整数值的集合抽象数据结构, 它可以保存类型为 int16_t , int32_t 或者 int64_t 的整数值, 并且保证集合中不会出现重复元素.

1. 整数集合的定义

每个 intset.h/intset 结构表示一个整数集合:

typedef struct intset {
  // 编码方式
  uint32_t encoding;
  // 集合包含的元素数量
  uint32_t length;
  // 保存元素的数组
  int8_t contents[];
} intset;

contents 数组是整数集合的底层实现: 整数集合的每个元素都是 contents 数组的一个数组项 (item) , 各个项在数组中按值的大小从小到大有序地排列, 并且数组中不包含任何重复项.

length 属性记录了整数集合包含的元素数量, 也即是 contents 数组的长度.

虽然 intset 结构将 contents 属性声明为 int8_t 类型的数组, 但实际上 contents 数组并不保存任何 int8_t 类型的值 -- contents 数组的真正类型取决于 encoding 属性的值:

  • 如果 encoding 属性的值为 INTSET_ENC_INT16 , 那么 contents 就是一个 int16_t 类型的数组, 数组里的每个项都是一个 int16_t 类型的整数值 (最小值为 -32,768 , 最大值为 32,767 );
  • 如果 encoding 属性的值为 INTSET_ENC_INT32 , 那么 contents 就是一个 int32_t 类型的数组, 数组里的每个项都是一个 int32_t 类型的整数值 (最小值为 -2,147,483,648 , 最大值为 2,147,483,647 );
  • 如果 encoding 属性的值为 INTSET_ENC_INT64 , 那么 contents 就是一个 int64_t 类型的数组, 数组里的每个项都是一个 int64_t 类型的整数值 (最小值为 -9,223,372,036,854,775,808 , 最大值为 9,223,372,036,854,775,807 ).

2. 升级

每当我们要将一个新元素添加到整数集合里面, 并且新元素的类型比整数集合现有所有元素的类型都要长时, 整数集合需要先进行升级 (upgrade) , 然后才能将新元素添加到整数集合里面.

升级整数集合并添加新元素共分为三步进行:

  1. 根据新元素的类型, 扩展整数集合底层数组的空间大小, 并为新元素分配空间;
  2. 将底层数组现有的所有元素都转换成与新元素相同的类型, 并将类型转换后的元素放置到正确的位上, 而且在放置元素的过程中, 需要继续维持底层数组的有序性质不变;
  3. 将新元素添加到底层数组里面.

因为每次向整数集合添加新元素都可能会引起升级, 而每次升级都需要对底层数组中已有的所有元素进行类型转换, 所以向整数集合添加新元素的时间复杂度为 O(N).

3. 升级之后新元素的摆放位置

因为引发升级的新元素的长度总是比整数集合现有所有元素的长度都大, 所以这个新元素的值要么就大于所有现有元素, 要么就小于所有现有元素:

  1. 在新元素小于所有现有元素的情况下, 新元素会被放置在底层数组的最开头 (索引 0 ) ;
  2. 在新元素大于所有现有元素的情况下, 新元素会被放置在底层数组的最末尾 (索引 length-1 ).

4. 升级的好处

4.1 提升灵活性

因为 C 语言是静态类型语言, 为了避免类型错误, 我们通常不会将两种不同类型的值放在同一个数据结构里面.

比如说, 我们一般只使用 int16_t 类型的数组来保存 int16_t 类型的值, 只使用 int32_t 类型的数组来保存 int32_t 类型的值, 诸如此类.

但是, 因为整数集合可以通过自动升级底层数组来适应新元素, 所以我们可以随意地将 int16_t , int32_t 或者 int64_t 类型的整数添加到集合中, 而不必担心出现类型错误, 这种做法非常灵活.

4.2 节约内存

当然, 要让一个数组可以同时保存 int16_t , int32_t , int64_t 三种类型的值, 最简单的做法就是直接使用 int64_t 类型的数组作为整数集合的底层实现. 不过这样一来, 即使添加到整数集合里面的都是 int16_t 类型或者 int32_t 类型的值, 数组都需要使用 int64_t 类型的空间去保存它们, 从而出现浪费内存的情况.

而整数集合现在的做法既可以让集合能同时保存三种不同类型的值, 又可以确保升级操作只会在有需要的时候进行, 这可以尽量节省内存.

比如说, 如果我们一直只向整数集合添加 int16_t 类型的值, 那么整数集合的底层实现就会一直是 int16_t 类型的数组, 只有在我们要将 int32_t 类型或者 int64_t 类型的值添加到集合时, 程序才会对数组进行升级.

5. 降级

整数集合不支持降级操作, 一旦对数组进行了升级, 编码就会一直保持升级后的状态.

6. 整数集合 API

函数 作用 时间复杂度
intsetNew 创建一个新的整数集合. O(1)
intsetAdd 将给定元素添加到整数集合里面. O(N)
intsetRemove 从整数集合中移除给定元素. O(N)
intsetFind 检查给定值是否存在于集合. 因为底层数组有序,查找可以通过二分查找法来进行, 所以复杂度为 O(log N).
intsetRandom 从整数集合中随机返回一个元素. O(1)
intsetGet 取出底层数组在给定索引上的元素. O(1)
intsetLen 返回整数集合包含的元素个数. O(1)
intsetBlobLen 返回整数集合占用的内存字节数. O(1)

7. 总结

  • 整数集合是集合键的底层实现之一.
  • 整数集合的底层实现为数组, 这个数组以有序, 无重复的方式保存集合元素, 在有需要时, 程序会根据新添加元素的类型, 改变这个数组的类型.
  • 升级操作为整数集合带来了操作上的灵活性, 并且尽可能地节约了内存.
  • 整数集合只支持升级操作, 不支持降级操作.

文章来源于本人博客,发布于 2018-05-28,原文链接:https://imlht.com/archives/138/

标签:元素,Redis,整数,类型,数组,集合,新元素
From: https://www.cnblogs.com/lofanmi/p/17085718.html

相关文章

  • CentOS使用 yum 安装 Redis
    1.下载fedora的epel仓库yuminstallepel-release2.安装redisyuminstallredis3.查看redis状态安装完毕后需要启动#启动redisserviceredisstart#停止rediss......
  • redis的几种并发场景的问题及解决策略
    简介 redis作为应用与数据库的中间缓存,用户访问数据源会首先访问redis,查询无数据则直接查询数据库,查询到后,返回的数据会加载到redis里面。在使用的过程中,redis在并发场景,存......
  • redis事务
    redis事务不支持完整的acid机制,redis事务的流程分为组队和执行的流程,组队的过程某条命令发生错误,则全部报错,执行过程发生错误,仍继续执行,除了执行失败的命令之外,继续执行,没有......
  • redis事务
    redis事务不支持完整的acid机制,redis事务的流程分为组队和执行的流程,组队的过程某条命令发生错误,则全部报错,执行过程发生错误,仍继续执行,除了执行失败的命令之外,继续执行,没有......
  • Redis
    Redis缓存常见问题缓存穿透--查询一定不存在数据缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存......
  • continue 求1~100之间,除了能被7整除之外的整数和
     上代码只要i循环到可以被7整除的数就会被忽略 <script>leta=0;for(leti=1;i<=100;i++){if(i%7==0){......
  • 【Redis】配置文件详解
    目录单位:Redis配置对大小写不敏感!包含:搭建Redis集群时,可以使用includes包含其他配置文件网络:通用GENERAL快照(RDB):持久化,在规定的时间内,执行了多少次操作则会持久化到文件.r......
  • 【Redis】事务和乐观锁如何实现?
    目录前言Redis如何实现事务?正常执行事务放弃事务编译时异常,代码有问题,或者命令有问题,所有的命令都不会被执行运行时异常,除了语法错误不会被执行且抛出异常后,其他的正确命令......
  • 【Redis】三大特殊数据类型
    目录Geospatial:地理位置Geospatial:地理位置城市经纬度查询:经纬度查询注意点1:两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!注意点2:有效的......
  • 【Redis】五大数据类型
    目录String(字符串)添加、查询、追加、获取长度,判断是否存在的操作自增、自减操作截取、替换字符串操作设置过期时间、不存在设置操作mset、mget操作添加获取对象、getset操......