Redis实现之整数集合

 

整数集合

整数集合(insert)是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。举个栗子,如果我们创建一个只包含五个元素的集合键,并且集合中的所有元素都是整数值,那么这个集合键的底层实现就会是整数集合:

127.0.0.1:6379> SADD numbers 1 3 5 7 9
(integer) 5
127.0.0.1:6379> SMEMBERS numbers
1) "1"
2) "3"
3) "5"
4) "7"
5) "9"
127.0.0.1:6379> OBJECT ENCODING numbers
"intset"

    

整数集合的实现

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

intset.h

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

  

contents数组是整数集合的底层实现:整数集合的每个元素都是contents数组中的一个数组项,各个项在数组中按值的大小从小到大有序地排列,并且数组中不包含任何重复项。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)。

图1-1展示了一个整数集合示例:

图1-1   一个包含五个int6_t类型整数值的整数集合

  • 如果encoding属性的值为为INTSET_ENC_INT16,表示整数集合的底层实现为int16_t类型的数组,而集合保存的都是int16_t类型的整数值
  • length属性为5,表示整数集合包含5个元素
  • contents数组按从小到大的顺序保存着集合中的五个元素
  • 因为每个集合元素都是int16_t类型的整数值,所以contents数组的大小等于sizeof(int16_t)*5=16*5=80位

图1-2展示了另一个整数集合示例:

图1-2   一个包含四个int16_t类型整数值的整数集合

  • encoding属性的值为INTSET_ENC_INT64,表示整数集合的底层实现为int64_t类型的数组,而数组中保存的都是int64_t类型的整数值
  • length属性的值为4,表示整数集合包含四个元素
  • contents数组按从小到大的顺序保存着集合中的四个元素
  • 因为每个集合元素都是int64_t类型的整数值,所以contents数组的大小为sizeof(int64_t)*4=64*4=256位

虽然contents数组保存的四个整数值中,只有-2 675 256 175 807 981 027是真正需要用int64_t类型来保存的,而其他的1、3、5三个值都可以用int16_t类型来保存,不过根据整数集合的升级规则,当向一个底层为int16_t数组的整数集合添加一个int64_t类型的整数值时,整数集合已有的所有元素都会被转换成int64_t类型,所以contents数组保存的四个整数值都是int64_t类型的,不仅仅是 -2 675 256 175 807 981 027

升级

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

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

举个栗子,假设现在有一个INTSET_ENC_INT16编码的整数集合,集合中包含三个int16_t类型的元素,如图1-3所示

图1-3   一个包含三个int16_t类型的元素的整数集合

因为每个元素都占用16位空间,所以整数集合底层数组的大小为3*16=48位,图1-4展示了整数集合的三个元素在这48位里的位置

现在,假设我们要将类型为int32_t的整数值65 535添加到整数集合里面,因为65 535的类型int32_t比整数集合当前所有元素要长,所以在将65 535添加到整数集合之前,程序需要先对整数集合进行升级。升级首先要做的是,根据新类型的长度,以及集合元素的数量(包括要添加的新元素在内),对底层数组进行空间重分配

整数集合目前有三个元素,再加上新元素65 535,整数集合需要分配四个元素的空间,因为每个int32_t整数值需要占用32位空间,所以在空间重分配之后,底层数组的大小将是32*4=128位,如图1-5所示。虽然程序对底层数组进行了空间重分配,但数组原有的三个元素1、2、3仍然是int16_t类型,这些元素还保存在数组的前48位里面,所以程序接下来要做的就是将这三个元素转换成int32_t类型,并将转换后的元素放置到正确的位上面,而且在放置元素的过程中,需要维持底层数组的有序性质不变

 

图1-5   进行空间重分配之后的数组

首先,因为元素3在1、2、3、65535四个元素中排名第三,所以它将被移动到contents数组的索引2位置上,也即是数组64位至95位的空间内,如图1-6所示

图1-6   对元素3进行类型转换,并保存在适当的位置上

接着,因为元素2在1、2、3、65535四个元素中排名第二,所以它将被移动到contents元素的索引1的位置上,也即是数组的32位至63位的空间内,如图1-7

 

图1-7   对元素2进行类型转换,并保存在适当的位上

之后,因为元素1在1、2、3、65536四个元素中排名第一,所以它将被移动到contents数组的索引0位置上,即数组的0位至31位空间内,如图1-8所示

图1-8   对元素1进行类型转换,并保存在适当的位置上

然后,因为元素65535在1、2、3、65535四个元素中排名第四,所以它将被添加到contents数组的索引3位置上,也即时数组的96位至127位的空间内,如图1-9所示

如图1-9   添加65535到数组

最后,程序将整数集合encoding属性的值从INTSET_ENC_INT16改为INTSET_ENC_INT32,并将length属性的值从3改为4,设置完成之后的整数集合如图1-10所示

 

图1-10   完成添加操作之后的整数集合

因为每次向整数集合添加新元素都可能会引起升级,而每次升级都需要对底层数组中已有的元素进行类型转换,所以向整数集合添加新元素的时间复杂度为O(N)。其他类型的升级操作,比如从INTSET_ENC_INT16编码升级为INTSET_ENC_INT64编码,或从INTSET_ENC_INT32编码升级为INTSET_ENC_INT64编码,升级的过程都和上面展示的升级过程类似

升级的好处

整数集合的升级策略有两个好处,一个是提升整数集合的灵活性,另一个是尽可能地节约内存

提升灵活性

因为C语言是静态类型语言,为了避免类型错误,我们通常不会将两种不同类型的值放在同一数据结构里面。例如,我们一般只用int16_t类型的数组来保存int16_t类型的值,只使用int32_t类型的数组来保存int32_t类型的值,诸如此类。但是,因为整数集合可以通过自动升级底层数组来适应新元素,所以我们可以随意地将int16_t、int32_t或int64_t类型的整数添加到集合中,而不必担心出现类型错误

节约内存

要让一个数组可以同时保存int16_t、int32_t、int64_t三种类型的值,最简单的做法就是直接使用int64_t类型的数组作为整数集合的底层实现。不过这样一来,部分可以用int16_t、int32_t来存储的值因使用int64_t类型来存储,会造成内存的浪费。而整数集合现在的做法既可以让集合能同时保存三种不同类型的值,又可以确保升级操作只会在有需要的时候进行,这可以尽量节省内存

降级

整数集合不支持降级操作,一旦对数组进行了升级,编码就会一直保持升级后的状态。举个栗子,对于图1-11所示的整数集合来说,即使我们将集合里唯一一个真正需要使用int64_t类型来保存的元素4 294 967 295删除了,整数集合的编码仍然会维持使用int64_t,底层数组也仍然会使int64_t类型,如图1-12所示

图1-11   数组编码为INTSET_ENC_INT64的整数集合

图1-12   删除4 294 967 295的整数集合

整数集合API

表1-1列出了整数集合的操作API

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

转载于:https://www.cnblogs.com/beiluowuzheng/p/9734227.html

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

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

相关文章

启动rocketmq 报错_RocketMQ为什么要保证订阅关系的一致性?

前段时间有个朋友向我提了一个问题,他说在搭建 RocketMQ 集群过程中遇到了关于消费订阅的问题,具体问题如下:然后他发了报错的日志给我看:the consumers subscription not exist我第一时间在源码里找到了报错的位置:or…

scala rest_使用路标的Scala和Java的Twitter REST API

scala rest如果您已经阅读了此博客上的其他文章,您可能会知道我喜欢创建各种数据集的可视化。 我刚刚开始一个小项目,在这里我想可视化来自Twitter的一些数据。 为此,我想直接从Twitter检索有关关注者的信息和个人资料信息。 我实际上开始寻找…

MySql中关于某列中相同数值连续出现次数的统计

MySql中关于某列中相同数值连续出现次数的统计 原表如下: www.2cto.com 100 101 102 100 100 103 104 102 102 105 106 101 101 输出如下: www.2cto.com 100 1 101 2 102 3 100 4 100 4 103 5 104 6 10…

设计模式之- 外观模式(Facade Pattern)

外观模式 外观模式(Facade Pattern):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式&…

python的while和for循环

while语句,提供了编写通用循环的一种方法,而for语句是用来遍历序列对象内的元素,并对每个元素运行一个代码块。break,continue用在循环内,跳出整个循环或者跳出一次循环。 一、while循环 1、一般格式 格式:首行以及测试…

go build 无文件_GO笔记之详解GO的编译执行流程

上篇文章介绍了Golang在不同系统下的安装,并完成了经典的Hello World案例。在这个过程中,我们用到了go run命令,它完成源码从编译到执行的整个过程。今天来详细介绍下这个过程。简单理解,go run 可等价于 go build 执行。 build命…

使用Spring Security和jdbc的Spring Boot第2部分

在上一篇文章中,我们基于Spring Security发出请求的默认表架构实现了安全性。 考虑到用户和角色,应用程序开发人员使用适合其需求的架构。 Spring使我们能够指定所需的查询,以便检索用户名,密码和角色等信息。 我们的自定义表将…

MySQL的一些简单语句

mysql 统计 表的数量:SELECT COUNT(1) FROM information_schema.TABLES WHERE TABLE_SCHEMA 你的数据库; MySQL的一些基础语句: 行是记录 列是字段 创建库 CREATE DATABASE [IF NOT EXISTS] 数据库名 [参数[ 参数] [ 参数]...]; 参数: CHARACTER …

【题解】Atcoder ARC#90 F-Number of Digits

Atcoder刷不动的每日一题... 首先注意到一个事实&#xff1a;随着 \(l, r\) 的增大&#xff0c;\(f(r) - f(l)\) 会越来越小。考虑暴力处理出小数据的情况&#xff0c;我们可以发现对于左端点 \(f(l) < 7\) 的情况下&#xff0c;右端点的最大限度为 \(\frac{10^8}{8} 10^7…

java分页查询_面试官:数据量很大,分页查询很慢,有什么优化方案?

准备工作一般分页查询使用子查询优化使用 id 限定优化使用临时表优化关于数据表的id说明《Java 2019 超神之路》《Dubbo 实现原理与源码解析 —— 精品合集》《Spring 实现原理与源码解析 —— 精品合集》《MyBatis 实现原理与源码解析 —— 精品合集》《Spring MVC 实现原理与…

Python逐行读取文件内容

f open("foo.txt") # 返回一个文件对象 line f.readline() # 调用文件的 readline()方法 while line:print line, # 后面跟 , 将忽略换行符# print(line, end )   # 在 Python 3中使用line f.readline()f.close() 也…

原型模式精讲

原型模式是一种创建型模式,也是属于创建对象的一种方式,像西游记里面的孙悟空吹猴毛也属于原型模式,克隆出来了一群的猴子猴孙,还有细胞的分裂,spring中的Bean的生命周期好像有一个单例还有个原型&#xff0c;那个原型就是每次请求都复制一个对象出来,官方的定义是:用原型实例指…

Python中map()函数浅析

MapReduce的设计灵感来自于函数式编程&#xff0c;这里不打算提MapReduce&#xff0c;就拿python中的map()函数来学习一下。 文档中的介绍在这里&#xff1a; map(function, iterable, ...) Apply function to every item of iterable and return a list of the results. If ad…

选择与循环:剪刀石头布_Python之石头剪刀布小游戏(史上最详细步骤)

​嗨&#xff0c;各位好呀&#xff0c;我是真小凡。相信你如果是一个刚学习Python的小白&#xff0c;一定会很想做一个自己的Python小游戏&#xff08;我就是这样子的&#xff09;&#xff0c;那么今天我们就一起实操一下&#xff01;首先要清楚&#xff0c;做一个项目必须的流…

cometd_CometD:Java Web应用程序的Facebook类似聊天

cometd聊天就像吃一块蛋糕或喝一杯热咖啡一样容易。 您是否曾经考虑过自己开发聊天程序&#xff1f; 您知道&#xff0c;聊天不容易。 但是&#xff0c;如果您是开发人员&#xff0c;并且阅读了本文的最后部分&#xff0c;则可以尝试自行开发一个聊天应用程序&#xff0c;并允许…

常用的C#正则表达式!

"^\d$" //非负整数&#xff08;正整数 0&#xff09; "^[0-9]*[1-9][0-9]*$" //正整数 "^((-\d)|(0))$" //非正整数&#xff08;负整数 0&#xff09; "^-[0-9]*[1-9][0-9]*$" //负整数 "^-?\d$" //整数 "^\d(\.\d)…

java excel 导出图片_JAVA 使用 POI 导出 EXCEL 自定义背景颜色

开发中常用表格导入和导出 Excel 是常见的功能。在这里分享下使用 POI 导出表格的简单实现&#xff0c;也是为大家提供个思路吧&#xff0c;抛砖引玉&#xff0c;话不多说直接上代码。1、项目引入 maven 依赖<!-- 2、导出表格数据接口RequestMapping3、导出效果如下图以上就…

清北学堂Day 3 游记

爆炸&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 上午&#xff1a;emmmm我今天要争取进前40&#xff08;flag 1&#xff09; 拿到试题&#xff0c;瞬间感受到了zhx长者的恶意......两道方案数题&#xff0c;我要凉了啊。 T1:这是道傻逼题&#xff0c;我20分…

隐马尔可夫模型(HMM)攻略

隐马尔可夫模型 (Hidden Markov Model&#xff0c;HMM) 最初由 L. E. Baum 和其它一些学者发表在一系列的统计学论文中&#xff0c;随后在语言识别&#xff0c;自然语言处理以及生物信息等领域体现了很大的价值。平时&#xff0c;经常能接触到涉及 HMM 的相关文章&#xff0c;一…

python中列表,元组,字符串如何互相转换

python中有三个内建函数&#xff1a;列表&#xff0c;元组和字符串&#xff0c;他们之间的互相转换使用三个函数&#xff0c;str(),tuple()和list(),具体示例如下所示: >>> s "xxxxx" >>> list(s) [x, x, x, x, x] >>> tuple(s) (x, x, …