Redis笔记(7):集合对象的结构与实现

1.整数集合

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

Redis笔记(7):集合对象的结构与实现

  • encoding属性决定contents数组的具体类型。
  • length属性记录了整数集合包含的元素数量,也即是contents数组的长度。
  • contents数组是整数集合的底层实现,整数集合的每个元素都是contents数组的一个数组项,各个项在数组中按值的大小从小到大有序地排列,并且数组中不包含任何重复项。

虽然intset结构将contents属性声明为int8_t类型的数组,但实际上contents数组并不保存任何int8_t类型的值,contents数组的真正类型取决于encoding属性的值:

  • 如果encoding属性的值为INTSET_ENC_INT16,那么contents就是一个int16_t类型的数组,数组里的每个项都是一个int16_t的整数值(最小值为-32768,最大值为32767)。
  • 如果encoding属性的值为INTSET_ENC_INT32,那么contents就是一个int32_t类型的数组,数组里的每个项都是一个int32_t的整数值(最小值为-2147483648,最大值为2147483647)。
  • 如果encoding属性的值为INTSET_ENC_INT64,那么contents就是一个int64_t类型的数组,数组里的每个项都是一个int64_t的整数值(最小值为-9223372036854775808,最大值为9223372036854775807)。

2.类型升级

(1)升级的过程

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

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

因为每次向整数集合添加新元素都可能会引起升级,而每次升级都需要对底层数组中已有的所有元素进行类型转换,所以向整数集合添加新元素的复杂度为O(N)。

(2)升级的好处

提升灵活性:因为C语言是静态类型语言,为了避免类型错误,我们通常不会将两种不同类型的值放在同一个数据结构里面。但是,因为整数集合可以通过自动升级底层数组来适应新元素,所以可以同时将int16_t、int32_t或int64_t类型的整数添加到集合中,且不会出现类型错误,这种做法非常灵活。

节约内存:整数集合的做法既可以让集合能同时保存三种不同类型的值,又可以确保升级操作只会在有需要的时候进行,这可以尽量节省内存。

(3)降级

整数集合不支持降级操作,一旦对数组进行了升级,编码就会一直保持升级后的状态。

3.集合对象实现方式

(1)编码类型

集合对象的编码可以是intset或者hashtable。

intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里面,intset编码的集合对象结构如下。

Redis笔记(7):集合对象的结构与实现

hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为Null,hashtable编码的集合对象结构如下。

Redis笔记(7):集合对象的结构与实现

(2)编码的转换

当集合对象可以同时满足以下两个条件时,对象使用intset编码,否则使用hashtable编码。

  • 集合对象保存的所有元素都是整数值;
  • 集合对象保存的元素数量不超过512个;

第二个条件的上限值是可以修改的,修改配置文件中set-max-intset-entries选项。当使用intset编码所需的两个条件的任意一个不能被满足时,就会执行对象的编码转换操作,原本保存在整数集合中的所有元素都会被转移并保存到字典里面,并且对象的编码也会从intset变为hashtable。