1、什么是整数集合?
整数集合(intset)是集合键的底层实现之一,是 Redis 用于保存整数值的集合抽象数据结构。
(集合对象(set)的编码可以是 intset
或者 hashtable
)
2、既然集合对象set底层有2种数据结构实现,那么什么时候用整数集合呢?
当集合对象可以同时满足以下两个条件时, 对象使用 intset
编码:
- 集合对象保存的所有元素都是整数值;
- 集合对象保存的元素数量不超过
512
个;(这个512是可以通过配置文件修改的,不用记)
不能满足这两个条件的集合对象需要使用 hashtable
编码(底层转换,把intset种的数据转移到hashtable里)。
3、这个数据结构的定义是什么样的?
typedef struct intset {
uint32_t encoding; // 编码方式
uint32_t length; // 元素数量,即 contents 数组的长度
int8_t contents[]; // 保存元素的数组
} intset;
4、编码方式encoding是什么意思?为什么一个数据结构会有这么个属性呢?
encoding
属性的值可以是 INTSET_ENC_INT16、
INTSET_ENC_INT32、INTSET_ENC_INT64
一句话总结:intset可以存储 int16、int32、int64 这3种类型的数据。至于具体存的是哪个类型,又encoding指定。
5、可是明明 contents[]指定的类型是int8啊?怎么存int16、int32、int64啊?
原文:
虽然 intset
结构将 contents
属性声明为 int8_t
类型的数组, 但实际上 contents
数组并不保存任何 int8_t
类型的值
原文并没有讲原因,以下是我的理解:
类比Go的string类型,底层是[]byte。存int16就用2个int8表示就行呗,int8这里可以看成一个单位。
6、contents里的数据有没有排序?有的话什么顺序?
contents
数组按从小到大的顺序保存着集合中的元素。
7、Redis中的set类型无序且不重复,为啥contents要排序?
数组 + 有序排列,你想到了什么?
举例一个set的用法,类似编程语言中的某些集合的 contains() 方法。
SISMEMBER key member :判断 member 元素是否集合 key 的成员(人话就是member是否包含在这个set中)
8、可是我还是不明白为什么一个破数组要分那么多类型存储?
你用mysql一个varchar(4000)存性别字段,有问题么?除了别人会骂你脑瘫,严格意义上问题不大。
但是redis不行。redis基于内存,内存很贵,内存有限。坦白讲:不管大数小数都用int64存其实没什么问题。
所以你会在书中多次看到这几个字:节省内存。
这也是为什么redis底层用了这么多数据结构的原因,也许很多API也许不是时间复杂度最低的,但是他一定要找到一个时间复杂度和空间复杂度的平衡。毕竟基于内存,没办法追求极致的处理速度。
9、那你这么说,原来都是int16的值,突然插入一个int32的咋办?
专业名词:升级
原文:
每当我们要将一个新元素添加到整数集合里面, 并且新元素的类型比整数集合现有所有元素的类型都要长时,
整数集合需要先进行升级(upgrade), 然后才能将新元素添加到整数集合里面。
10、咋升级?
假设我们要将类型为 int32_t
的整数值添加到整数集合里面
11、既然有升级,那有降级么?
整数集合不支持降级操作, 一旦对数组进行了升级, 编码就会一直保持升级后的状态。
12、说了那么多,为什么要存在这种数据结构,有什么好处?
尽可能地节约内存。
因为整数集合可以通过自动升级底层数组来适应新元素, 所以我们可以随意地将 int16_t
、 int32_t
或者 int64_t
类型的整数添加到集合中, 而不必担心出现类型错误, 这种做法非常灵活。
整数集合现在的做法既可以让集合能同时保存三种不同类型的值, 又可以确保升级操作只会在有需要的时候进行, 这可以尽量节省内存。
13、集合对象保存的元素数量不超过 512
个,为什么要有这个限制?为什么是512?
效率问题,redis作者一定有自己的考量,intset有可取之处,但是不能解决大量数据的问题,显然数据量大用hashtable更好。
为什么是512,笔者猜测,干程序的就愿意整一些128啊 256啊这类的数,怎么地?513就一定效率低么,所以你也不用记。