深入理解Redis整数集合(intset)的升级策略:内存优化的核心魔法

引言

作为Redis中最节省内存的数据结构之一,整数集合(intset) 专门用于高效存储整型数据。但你可能不知道,它背后藏着一个精妙的「动态升级」机制——能在不浪费内存的前提下,灵活适配不同大小的整数。今天我们就来扒开这层神秘面纱,彻底搞懂它的升级策略!


一、先搞清楚:intset到底是啥?

在Redis中,当存储的整型数据比较「紧凑」(比如都是小整数)时,不会直接用普通的数组或哈希表,而是用更高效的 intset。它的核心设计目标就一个:用最小的内存存最多的整数

1.1 intset的底层数组结构

整数集合的底层结构定义(简化版)如下:

typedef struct intset {uint32_t encoding;  // 编码类型,决定元素的实际类型(如 INTSET_ENC_INT16/INT32/INT64)uint32_t length;    // 集合中元素的个数int8_t contents[];  // 存储元素的数组(实际类型由 encoding 决定)
} intset;

intset 的底层是一个数组(contents[]),但和普通数组不同,它的元素类型不是固定的,而是由一个「编码标识」(encoding)动态决定的。这个 encoding 有三种可能:

  • INTSET_ENC_INT16:元素是16位有符号整数(范围:-32768 ~ 32767),每个元素占2字节;
  • INTSET_ENC_INT32:元素是32位有符号整数(范围:-2^31 ~ 2^31-1),每个元素占4字节;
  • INTSET_ENC_INT64:元素是64位有符号整数(范围:-2^63 ~ 2^63-1),每个元素占8字节。

举个栗子:如果一个 intsetencodingINTSET_ENC_INT16,那它的 contents 数组里存的每个数都是16位的,占2字节。

1.2 为什么需要升级?内存优化的核心

假设你有一个 intset,初始存储的都是1000以内的小整数(完全在int16范围内),这时候用int16编码,每个元素只占2字节,内存利用率极高。但如果突然要插入一个40000的数(超过int16的最大值32767),这时候怎么办?

直接扩容数组?不行! 因为int16的数组每个位置只能存2字节,40000用int16存会溢出(变成负数)。所以必须升级 encoding 到更大的类型(比如int32),让所有元素都能被正确存储。

这就是 intset 升级的核心意义:动态调整编码类型,用最小的内存兼容所有元素


二、升级什么时候触发?惰性策略的智慧

intset 的升级不是「每次插入都检查」,而是「按需触发」——只有当你插入一个「当前编码存不下」的整数时,才会触发升级。这种「惰性策略」能避免频繁的内存分配和数据迁移,提升性能。

触发条件示例:

  • 当前 encodingINTSET_ENC_INT16(存16位整数),插入一个32768(超过int16最大值32767)→ 触发升级到 INTSET_ENC_INT32
  • 当前 encodingINTSET_ENC_INT32,插入一个2^32(超过int32最大值2147483647)→ 触发升级到 INTSET_ENC_INT64
  • 插入的数在当前编码范围内(比如int16存20000)→ 不升级,直接插入。

三、升级全过程拆解:从int16到int32的「变身」

升级过程听起来简单,但背后涉及内存分配、数据类型转换、元素迁移等步骤。我们以「int16升级到int32」为例,一步步看:

3.1 步骤1:确定目标编码类型

首先得判断新插入的数需要多大的空间。比如插入40000,它超过了int16的范围(-3276832767),但能被int32容纳(-2^312^31-1),所以目标编码是 INTSET_ENC_INT32

3.2 步骤2:计算新内存大小

原来的 intset 有3个int16元素(每个2字节),总内存是3×2=6字节。现在要插入1个int32元素(4字节),所有元素都要转为int32(每个占4字节),所以新内存大小是(3+1)×4=16字节。

3.3 步骤3:分配新内存并迁移数据

这一步是关键!Redis会做两件事:

  1. 分配新内存:根据计算出的16字节,申请一块新的连续内存空间;
  2. 转换并复制旧数据:把原来的3个int16元素逐个转为int32(比如10→(int32_t)10,值不变但类型升级),复制到新内存中。

3.4 步骤4:插入新元素并保持有序

intset 中的元素始终是升序排列的(方便二分查找)。所以插入40000时,需要找到正确的位置(这里应该是最后),插入后数组变为 [10, 20, 30, 40000]

3.5 步骤5:更新元数据

最后,把 encoding 改为 INTSET_ENC_INT32length 加1(现在集合有4个元素)。至此,升级完成!


四、升级的三大关键特性:为什么这样设计?

4.1 类型一致性:升级后「一刀切」

升级完成后,集合里所有元素的类型都统一为目标编码。比如从int16升级到int32后,后续插入的元素如果超过int32范围,会再次触发升级到int64。这保证了数据存储的一致性,避免了混合类型的复杂处理。

4.2 内存优化:用多少,扩多少

升级只在必要时触发,避免了「为了存大数而预分配大内存」的浪费。比如存1000个int16整数,只需要2000字节;如果提前升级到int32,就需要4000字节——显然前者更省内存。

4.3 不可降级:升级容易,降级难

一旦升级到更大的编码(比如int16→int32),即使后续删除了所有大整数,encoding 也不会降回去。这是Redis的「性能妥协」:频繁检查是否可降级会增加开销,而保留大编码对后续插入大数更友好(不需要再次升级)。


五、示例演示:直观感受升级过程

假设我们有一个初始状态为 INTSET_ENC_INT16intset,存储 [10, 20, 30](3个元素,内存6字节):

场景1:插入int16范围内的数(如25)

  • 25在int16范围内(-32768~32767),无需升级;
  • 直接插入到正确位置(升序),集合变为 [10, 20, 25, 30]length=4,内存占用8字节(4×2)。

场景2:插入int32范围的数(如40000)

  • 40000超过int16最大值32767,触发升级到 INTSET_ENC_INT32
  • 新内存大小=(3+1)×4=16字节;
  • 旧数据转换为int32后复制到新内存,插入40000,集合变为 [10, 20, 30, 40000]
  • encoding 改为 INTSET_ENC_INT32length=4。

总结:intset升级策略的「得与失」

Redis的 intset 升级策略是一个典型的「空间换时间」与「时间换空间」的平衡设计:

  • 优点:通过动态升级,既保证了小整数的内存效率(用int16存小数),又能兼容大整数(升级到int32/int64);
  • 缺点:升级需要重新分配内存并复制数据,时间复杂度是O(N)(N是原元素数量),频繁插入大数可能影响性能。

理解这一策略后,我们在使用Redis时可以更「聪明」:如果确定要存储大量小整数(如1~1000),可以用 intset 节省内存;如果需要混合存储大整数,建议直接用 hashstring 类型,避免频繁升级带来的开销。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/88480.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/88480.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高性能计算(HPC)集群和工作流:intel-oneapi-hpc-toolkit安装与使用

成功安装了 Intel oneAPI HPC Toolkit!这个工具包包含了很多强大的工具,可以帮助你优化和加速高性能计算(HPC)任务,特别是在使用 Intel 的硬件(如 Xeon 处理器和 GPU)时。 接下来,…

QT vscode cmake 编译 undefined reference to `vtable for 问题解决

编译时出现undefined reference to vtable for 问题,是没有添加头文件到目标,添加即可: 如果使用的是qt5, 没有qt_add_executable, 使用qt 5的 自动处理即可: # 启用 Qt 自动处理功能 set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC …

linux内核奔溃转储之kexec、kdump

一、kexec是什么? kexec 是 Linux 内核提供的一种关键技术,允许系统‌在不经过完整硬件重启(BIOS/UEFI 初始化)的情况下,直接从当前正在运行的内核加载并启动另一个新内核‌。以下是其核心要点: ‌定义与核…

标题:2025金融护网行动实战指南:从合规防御到智能免疫的体系化进阶

引言 2025年,随着《中国人民银行业务领域网络安全事件报告管理办法》正式实施,金融护网行动已从“合规检查”升级为“能力对抗”。面对AI驱动的自适应攻击、勒索病毒与黑灰产协同威胁,金融机构需构建“技术-管理-人才”三位一体的智能防御体…

NEO4j的安装部署

windows neo4j新版本安装需要部署jdk17,下面这个版本是jdk8最新的支持版本 neo4j-community-3.5.9-windows.zipIndex of /doc/neo4j/3.5.9/ 启动 dos面板中启动 neo4j.bat console linux neo4j新版本安装需要部署jdk17,下面这个版本是jdk8最新的支…

八股文——JAVA基础:说一下C++与java的区别

首先,c与java都是面向对象编程,都包含封装、继承、多态的特性。但是c多继承,而java只能单继承与多实现。 其次,java无法直接访问内存,java通过引用对向,比如new一个对象,拿到的对象实例实际上是…

Vue3 Composition API 深度解析:告别Options API的局限性

目录 一、为什么需要Composition API? 二、核心概念:setup() 函数 三、响应式核心:ref() 和 reactive() 1. ref - 处理基本类型/对象 2. reactive - 处理对象 四、生命周期钩子新写法 五、强大的逻辑复用:组合式函数 六、响…

IoT/HCIP实验-5/基于NB-IoT的智慧农业实验(平台侧开发+端侧编码+基础调试分析)

文章目录 概述扩展板 E53_IA1智慧农业平台测开发功能定义/模型开发编解码插件开发-消息编解码插件开发-关联编解码插件开发-部署注册实际设备 智慧农业端侧编码工程配置数据结构定义数据收集任务数据上报任务设备接入过程正确设置接入参数命令响应任务 程序调试其他 概述 本实…

多网络环境vmware虚拟机配置

环境:一台台式机、一台笔记本、笔记本中安装虚拟机。台式机及笔记本都使用wifi连接。 实现效果:虚拟机采用固定ip方式,台式机可以直接连接虚拟机。 1、VMware环境配置 台式机ip:192.168.31.43 笔记本ip:192.168.31.…

ZArchiver×亚矩云手机:云端文件管理的“超维解压”革命

在数字化办公与移动应用生态中,文件压缩与解压是高频刚需场景,但传统本地工具受限于设备性能、存储空间及跨平台协作痛点。ZArchiver(轻量级压缩工具)与亚矩云手机的结合,通过“云端算力虚拟化环境”的创新模式&#x…

微帧WZVQA:极致还原人眼感知,精准评估视频画质

随着移动互联网的不断发展以及智能手机的普及,短视频已逐步取代图片和文字,跻身主流媒体形式的前列。短视频平台的兴起,让数十亿用户可以制作,分享并接收彼此的信息,为人们开辟了一条全新的知识获取途径。然而&#xf…

信创 CDC 实战|国产数据库的数据高速通道:OceanBase 实时入仓 StarRocks

国产数据库加速进入核心系统,传统同步工具却频频“掉链子”。本系列文章聚焦 OceanBase、GaussDB、TDSQL、达梦等主流信创数据库,逐一拆解其日志机制与同步难点,结合 TapData 的实践经验,系统讲解从 CDC 捕获到实时入仓&#xff0…

Unity 通过AVProMovieCapture插件实现摄像机录屏

1.AVProMovieCapture插件下载 没什么好说的,搞到安装包之后,直接往项目中拉就行。 2.操作面板配置 (1)在Hierarchy创建一个空物体,上面添加Capture From Camera和Camera Selector两个插件 (2&#xff09…

深度学习:PyTorch卷积神经网络分享(1)

本文目录: 一、CNN概述二、CNN日常应用三、CNN的卷积层(一 )基本介绍(二)卷积层计算1.对输入数据的要求2.卷积核核心参数3.计算过程4.特征图尺寸计算5.1、多通道卷积计算5.2、多卷积核计算6.PyTorch卷积层API 前言&…

Cesium添加3dtiles并平移到指定经纬度

访问tileset.json,查看root.transform,12,13,14分别代表模型参考原点的地心坐标Cartesian3(x,y,z) let tileset await Cesium.Cesium3DTileset.fromUrl()构造origin_cartesian3new Cesium.Cartesian3(x,y,z) 设置待平移到的位置经纬高为longitude,latitude,height,例如(116,…

STM32G070x 单片机项目代码解析:基于 HAL 库的嵌入式系统开发

项目总体架构 该项目采用标准的 STM32 工程结构,主要包含以下几个部分: 头文件包含:系统头文件和用户自定义头文件外设句柄定义:SPI、TIM、UART 等外设的句柄声明用户自定义变量:LED 控制、按键状态等标志位初始化函数…

winform mvvm

if (!mvvmContext1.IsDesignMode) InitializeBindings(); 这段代码的意思是:如果当前应用程序不是处于设计模式(即程序正在运行),就调用InitializeBindings方法来初始化视图与视图模型之间的绑定。 void Initiali…

防火墙快速管理软件,66K超小巧

软件介绍 今天为大家推荐一款轻量级的Windows防火墙管理工具,这款工具能帮助用户快速开启或关闭系统防火墙功能,操作比系统原生设置更加便捷高效。 软件优势 相比通过系统设置层层点击的操作方式,这款仅66KB大小的微型工具只需单击按钮…

python中的高级变量III

python中的高级变量III 删除列表元素(list)扩展知识点 name_list ["Mike","John","Alice"] del name_list[1] # 通过del name_list[1]删除“John” print(name_list) # 输出 [Mike, Alice]注意:del …

深入理解Redis

深入理解Redis:高性能内存数据库的核心原理与应用实践 1. 引言 在现代互联网应用中,高性能、低延迟的数据访问是至关重要的。传统的关系型数据库(如MySQL)虽然功能强大,但在高并发场景下往往成为性能瓶颈。Redis&…