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

intset可以保存类型为int16_t,int32_t,int64_t的整数值,并且保证集合中不会出现重复元素。

整数集合的结构体定义在intset.h中:

typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;

encoding表示intset中保存的整数的编码,共有三种编码,分别是:INTSET_ENC_INT16、INTSET_ENC_INT32和INTSET_ENC_INT64。三种编码的定义如下:

#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

注意,编码在保存时是按照小端的方式保存的,也就是说在大端系统中,还需要将实际的编码值翻转之后,才能存储到encoding中,比如:is->encoding =intrev32ifbe(INTSET_ENC_INT16); 其中intrev32ifbe表示将32位整数按照字节进行翻转。

length表示intset中保存的整数个数。与encoding一样,它也是按照小端的方式保存的,在大端系统中,也需要将实际值翻转之后,才能存储到length中,比如:is->length = intrev32ifbe(len-1)。

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

虽然contents的类型是int8_t,但它可以保存类型为int16_t,int32_t,int64_t的整数值。可以将contents想象为一段连续的内存空间,该空间的实际大小是encoding*length。保存在contents中的整数,也是按照小端的方式保存的。

如果encoding属性的值为INTSET_ENC_INT16,那contents中保存的整数就是int16_t类型,范围是[-32768,32767]。如果encoding属性的值为INTSET_ENC_INT32,那contents中保存的整数就是int32_t类型,范围是[-2147483648, 2147483647]。如果encoding属性的值为INTSET_ENC_INT64,那contents中保存的整数就是int64_t类型,范围是[-9223372036854775808,9223372036854775807]。

将一个新元素添加到intset,且新元素的类型比整数集合现有所有元素的类型都要大时,整数集合需要先进行升级,然后才能将新元素添加到整数集合里面。升级intset并添加新元素的代码实现如下:

static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
uint8_t curenc = intrev32ifbe(is->encoding);
uint8_t newenc = _intsetValueEncoding(value);
int length = intrev32ifbe(is->length);
int prepend = value < 0 ? 1 : 0; /* First set new encoding and resize */
is->encoding = intrev32ifbe(newenc);
is = intsetResize(is,intrev32ifbe(is->length)+1); /* Upgrade back-to-front so we don't overwrite values.
* Note that the "prepend" variable is used to make sure we have an empty
* space at either the beginning or the end of the intset. */
while(length--)
_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc)); /* Set the value at the beginning or the end. */
if (prepend)
_intsetSet(is,0,value);
else
_intsetSet(is,intrev32ifbe(is->length),value);
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}

该函数只有在升级插入时才会调用,也就是说新值value超出了is现有的编码范围,因此,value要么插入在索引0,要么插入在索引is->length。

首先获取is当前的编码curenc,当前长度length,并根据value得到其合适的编码newenc,newenc成为is在升级插入value后的新编码;

prepend用于升级is时,定位contents中原有元素的新索引。如果value为正数,则value需要插入到索引is->length中,因此prepend为0,表明contents原有元素的索引值保持不变。注意这并不意味着contents的内容保持不变,因为编码升级了,每个元素所占的内存空间也发生了变化,因此还需要重新设置每个索引的元素;

若value为负数,则说明value要插入到索引0中,因此prepend为1,表明contents原有元素需要后移一个索引;

然后更新is的encoding为newenc,并调用intsetResize重新申请is的空间;根据新的编码newenc,将is的contents中的原有元素,根据prepend的值重新放置;

最后根据prepend值,将value插入到0或length处;并更新is的length属性。

注意,intset不支持降级操作,一旦对intset进行了升级,编码就会一直保持升级后的状态。比如一个intset保存了1,2,3,4四个数,这四个数使用编码INTSET_ENC_INT16即可,后插入了4294967295,intset升级编码为INTSET_ENC_INT64。现在如果再把4294967295删除了,则intset中,虽然还是1,2,3,4四个数,但是编码依然保持为INTSET_ENC_INT64。

有关intset其他的代码实现,可以参阅:

https://github.com/gqtc/redis-3.0.5/blob/master/redis-3.0.5/src/intset.c

Redis源码解析:06整数集合的更多相关文章

  1. redis源码学习_整数集合

    redis里面的整数集合保存的都是整数,有int_16.int_32和int_64这3种类型,和C++中的set容器差不多. 同时具备如下特点: 1.set里面的数不重复,均为唯一. 2.set里面的 ...

  2. Redis源码解析之跳跃表(三)

    我们再来学习如何从跳跃表中查询数据,跳跃表本质上是一个链表,但它允许我们像数组一样定位某个索引区间内的节点,并且与数组不同的是,跳跃表允许我们将头节点L0层的前驱节点(即跳跃表分值最小的节点)zsl- ...

  3. .Net Core缓存组件(Redis)源码解析

    上一篇文章已经介绍了MemoryCache,MemoryCache存储的数据类型是Object,也说了Redis支持五中数据类型的存储,但是微软的Redis缓存组件只实现了Hash类型的存储.在分析源 ...

  4. Redis源码解析:15Resis主从复制之从节点流程

    Redis的主从复制功能,可以实现Redis实例的高可用,避免单个Redis 服务器的单点故障,并且可以实现负载均衡. 一:主从复制过程 Redis的复制功能分为同步(sync)和命令传播(comma ...

  5. Redis源码解析:13Redis中的事件驱动机制

    Redis中,处理网络IO时,采用的是事件驱动机制.但它没有使用libevent或者libev这样的库,而是自己实现了一个非常简单明了的事件驱动库ae_event,主要代码仅仅400行左右. 没有选择 ...

  6. redis源码解析之内存管理

    zmalloc.h的内容如下: void *zmalloc(size_t size); void *zcalloc(size_t size); void *zrealloc(void *ptr, si ...

  7. Redis源码解析

    一.src/server.c 中的redisCommandTable列出的所有redis支持的命令,其中字符串命令包括从get到mget:列表命令从rpush到rpoplpush:集合命令包括从sad ...

  8. Redis源码解析:26集群(二)键的分配与迁移

    Redis集群通过分片的方式来保存数据库中的键值对:一个集群中,每个键都通过哈希函数映射到一个槽位,整个集群共分16384个槽位,集群中每个主节点负责其中的一部分槽位. 当数据库中的16384个槽位都 ...

  9. Redis源码解析:25集群(一)握手、心跳消息以及下线检测

    Redis集群是Redis提供的分布式数据库方案,通过分片来进行数据共享,并提供复制和故障转移功能. 一:初始化 1:数据结构 在源码中,通过server.cluster记录整个集群当前的状态,比如集 ...

随机推荐

  1. @ font-face 引入本地字体文件

    @font-face { font-family: DeliciousRoman; src: url('…/Delicious-Roman.otf'); font-stretch: condensed ...

  2. 数组的方法之(Array.prototype.forEach() 方法)

    forEach() 方法对数组的每个元素执行一次提供的函数. 注意: 没有返回一个新数组 并且 没有返回值! 应用场景:为一些相同的元素,绑定事件处理器! const arr = ['a', 'b', ...

  3. invalid use of null value

    给mysql的数据表的一个字段插入数据,不成功, 然后在数据表设计中,把不是null勾选上,又提示 invalid use of null value 这种情况比较尴尬 只能删掉这一个字段,然后新建一 ...

  4. python基础--常用的模块(collections、time、datetime、random、os、sys、json、pickle)

    collection模块: namedtuple:它是一个函数,是用来创建一个自定义的tuple对象的,并且规定了tuple元素的个数,并可以用属性而不是索引来引用tuple的某个元素.所以我们就可以 ...

  5. homebrew长时间停在Updating Homebrew 这个步骤

    在国内的网络环境下使用 Homebrew 安装软件的过程中可能会长时间卡在 Updating Homebrew 这个步骤. 例:执行 brew install composer 命令 ➜ ~ brew ...

  6. python 模拟实验

  7. Vue表单验证插件的制作过程

    一.表单验证模块的构成 任何表单验证模块都是由 配置――校验――报错――取值 这几部分构成的. 配置: 配置规则 和配置报错,以及优先级 校验: 有在 change 事件校验, 在点击提交按钮的时候校 ...

  8. golang之结构体

    Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性. Go语言通过用自定义的方式形成新的类型,结构体是类型中带有成员的复合类型. Go 语言中的类型可以被实例化,使用new或&a ...

  9. 大咖手把手教您,DLA一键建仓!

    DLA很早之前就支持了对关系型数据库的查询,但是一直以来用户会有一个担心: 直接分析RDS里面的数据会不会影响线上业务. 这个担心很合理,除非你要查询的RDS是专门用来做后台数据分析使用的,否则直接大 ...

  10. python 常规字符匹配