整数集合(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. ES6教程笔记

    ES介绍 什么是ES ES全称 EcmaScript 是一种脚本语言的规范 Javascript为ES的实现 Ecma 是一个组织 Ecma组织 为什么要学习ES6? ES6的版本变动内容最多,具有里 ...

  2. redission分布式redis锁使用

    public void lock(String key, List<Long> idx) { if (CollectionUtils.isEmpty(idx)) { return; } i ...

  3. Python 字典嵌套

    字典嵌套 含义:将一系列字典存储在列表中,或将列表作为值存储在字典中 在列表中嵌套字典.在字典中嵌套列表.在字典中嵌套字典 字典列表 创建多个字典,将字典存放在列表中 使用range()自动生成多个字 ...

  4. 一文掌握ArrayList和LinkedList源码解读

    大家好,我是Leo! 今天来看一下ArrayList和LinkedList的源码,主要是看一下常用的方法,包括像add.get.remove方法,大部分都是从源码直接解读的,相信大家读完都会有一定收获 ...

  5. 2020-08-20:GO语言中的协程与Python中的协程的区别?

    福哥答案2020-08-20: 1.golang的协程是基于gpm机制,是可以多核多线程的.Python的协程是eventloop模型(IO多路复用技术)实现,协程是严格的 1:N 关系,也就是一个线 ...

  6. 2021-07-30:两个有序数组间相加和的Topk问题。给定两个有序数组arr1和arr2,再给定一个整数k,返回来自arr1和arr2的两个数相加和最大的前k个,两个数必须分别来自两个数组。按照降

    2021-07-30:两个有序数组间相加和的Topk问题.给定两个有序数组arr1和arr2,再给定一个整数k,返回来自arr1和arr2的两个数相加和最大的前k个,两个数必须分别来自两个数组.按照降 ...

  7. Selenium - 元素定位(2) - XPATH进阶

    Selenium - 元素定位 XPATH 定位进阶 元素示例 属性定位 # xpath 通过id属性定位 driver.find_element_by_xpath("//*[@id='kw ...

  8. Golang指针隐式间接引用

    1.Golang指针 在介绍Golang指针隐式间接引用前,先简单说下Go 语言的指针 (Pointer),一个指针可以指向任何一个值的内存地址 它指向那个值的内存地址,在 32 位机器上占用 4 个 ...

  9. vscode 配置代码自动格式化加修复

    子曰:"工欲善其事,必先利其器", 编码必须的就是有一个顺手的ide,然而光有还不行,还要懂得配置,毕竟不同的团队代码规范不同,如目前用得较多的就是eslint,今天就顺便记录下v ...

  10. x86 机器指令编码规则

    x86 机器指令编码依次由一下部分组成: 指令前缀(prefix,非必需) 操作码(opcode,必需) 寻址方式 R/M(ModR/M,非必需) 比例因子-变址-基址(SIB,非必需) 地址偏移量( ...