整数集合(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的设计与实现(5)-整数集合的更多相关文章

  1. Redis源码解析:06整数集合

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

  2. Redis 基础设计结构之四 set(集合)

    Redis 有 5 种基础数据结构,分别为:string (字符串).list (列表).set (集合).hash (哈希) 和 zset (有序集合). 今天来说一下set(集合)这种存储结构,s ...

  3. redis 笔记01 简单动态字符串、链表、字典、跳跃表、整数集合、压缩列表

    文中内容摘自<redis设计与实现> 简单动态字符串 1. Redis只会使用C字符串作为字面量,在大多数情况下,Redis使用SDS(Simple Dynamic String,简单动态 ...

  4. Redis 学习笔记(篇四):整数集合和压缩列表

    整数集合 Redis 中当一个集合(set)中只包含整数,并且元素不多时,底层使用整数集合实现,否则使用字典实现. 那么: 为什么会出现整数集合呢?都使用字典存储不行吗? 整数集合在 Redis 中的 ...

  5. 图解Redis之数据结构篇——整数集合

    前言     整数集合(intset)并不是一个基础的数据结构,而是Redis自己设计的一种存储结构,是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时, Redis i ...

  6. 【redis】redis底层数据结构原理--简单动态字符串 链表 字典 跳跃表 整数集合 压缩列表等

    redis有五种数据类型string.list.hash.set.zset(字符串.哈希.列表.集合.有序集合)并且自实现了简单动态字符串.双端链表.字典.压缩列表.整数集合.跳跃表等数据结构.red ...

  7. 多图解释Redis的整数集合intset升级过程

    redis源码分析系列文章 [Redis源码系列]在Liunx安装和常见API 为什么要从Redis源码分析 String底层实现——动态字符串SDS 双向链表都不懂,还说懂Redis? 面试官:说说 ...

  8. Redis 底层数据结构之整数集合

    文章参考:<Redis 设计与实现>黄建宏 整数集合 整数集合时集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合数量不多时,就会使用整数集合 typedef struct i ...

  9. Redis原理再学习05:数据结构-整数集合intset

    intset介绍 intset 整数集合,当一个集合只有整数元素,且元素数量不多时,Redis 就会用整数集合作为集合键的底层实现. redis> SADD numbers 1 3 5 7 9 ...

  10. redis intset(整数集合)

    redis intset (整数集合) 概述 intset 是集合的底层实现结构之一 intset 集合只包含整数 intset 自升级 intset 整数集合是有序的 intset 结构 结构 // ...

随机推荐

  1. 循序渐进的掌握uni-app,两个小时完成一个简单项目——新闻App、新闻小程序

    效果图 一.创建项目 uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS.Android.Web(响应式).以及各种小程序(微信/支付宝/百度/头条/ ...

  2. Nginx部署多个vue前端项目

    前言:在前端项目的部署上需要让2个前端项目都部署到一个IP地址和端口下,那么我们这里就要用到Nginx了,接下来我们看看如何在一个Nginx下部署2个前端项目. 例如我的服务器地址是 http://1 ...

  3. C# ConfigureWait

    ConfigureAwait 参数为bool类型.true:尝试将延续任务封送回原始上下文 我们一般使用的是false,用于避免强制在原始上下文或调度程序中进行回调. 原理: 以await DoSom ...

  4. 实例化对象 A a = new A();

    "new" 在Java中代表实例化的意思, A a = new A()代表实例化了一个对象a, 这个对象a属于A类. 可以认为A是一个抽象概念, 对象a是一个实体(存储于内存), ...

  5. 虚拟机中Docker下部署gitlab

    一.安装Gitlab 1.拉取镜像并启动 由于服务器的80端口可能被占用,所以这里我们改成了其他端口来启动 docker run -d -p 2443:443 -p 5678:80 -p 2222:2 ...

  6. selenium web控件的交互进阶

    Action ActionChains: 执行PC端的鼠标点击,双击,右键,拖曳等事件 TouchActions: 模拟PC和移动端的点击,滑动,拖曳,多点触控等多种手势操作 动作链接 ActionC ...

  7. 文心一言 VS chatgpt (15)-- 算法导论3.2 4~5题

    四.函数 $\lceil \lg n \rceil !$ 多项式有界吗?函数 $\lceil \lg \lg n \rceil !$ 多项式有界吗? 文心一言: chatgpt: 对于第一个问题,函数 ...

  8. Linux基础 | 青训营笔记

    课程介绍 以下是Linux系统的相关知识(但是并不全部出现在本文中) 学习Linux的价值 Liux是现代化应用程序交付的首选平台,无论是部署在裸机.虚拟化还是容器化环境 公司内部服务(TCE.Faa ...

  9. 【重学C++】02 脱离指针陷阱:深入浅出 C++ 智能指针

    文章首发 [重学C++]02 脱离指针陷阱:深入浅出 C++ 智能指针 前言 大家好,今天是[重学C++]系列的第二讲,我们来聊聊C++的智能指针. 为什么需要智能指针 在上一讲<01 C++如 ...

  10. 一文教会你用Apache SeaTunnel Zeta离线把数据从MySQL同步到StarRocks

    在上一篇文章中,我们介绍了如何下载安装部署SeaTunnel Zeta服务(3分钟部署SeaTunnel Zeta单节点Standalone模式环境),接下来我们介绍一下SeaTunnel支持的第一个 ...