跟着大彬读源码 - Redis 10 - 对象编码之整数集合 (2)

而整数集合的升级操作,既能同时保存三种不同类型的值,又可以确保升级操作只会在有需要的时候进行,达到节省内存的目的。

4 交、并、差集算法

Redis 中的集合实现了交、并、差等操作,相关操作可参加 t_set.c,其中 sinterGenericCommand() 实现交集,sunionDiffGenericCommand() 实现并集和差集。

它们都能同时对多个集合进行元素。当对多个集合进行差集运算时,会先计算出第一个和第二个集合的差值,然后再与第三个集合做差集,依次类推。

接下来,我们一起来认识下三个操作的实现思路。

4.1 交集

计算交集的过程大概可以分为三部分:

检查各个集合,对于不存在的集合当做空集来处理。一旦出现空集,则不用继续计算了,最终的交集就是空集。

对各个集合按照元素个数由少到多进行排序。这个排序有利于后面计算的时候从最小的集合开始,需要处理的元素个数较少。

对排序后第一个集合(也就是最小集合)进行遍历,对于它的每一个元素,依次在后面的所有集合中进行查找。只有在所有集合中都能找到的元素,才加入到最后的结果集合中。

需要注意的是,上述第 3 步在集合中进行查找,对于 intset 和 dict 的存储来说时间复杂度分别是 O(log n) 和 O(1)。但由于只有小集合才使用 intset,所以可以粗略地认为 intset 的查找也是常数时间复杂度的。

4.2 并集

并集操作最简单,只要遍历所有集合,将每一个元素都添加到最后的结果集中即可。向集合中添加元素会自动去重,所以插入的时候无需检测元素是否已存在。

4.3 差集

计算差集有两种可能的算法,它们的时间复杂度有所区别。

第一种算法

对第一个集合进行遍历,对于它的每一个元素,依次在后面的所有集合中进行查找。只有在所有集合中都找不到的元素,才加入到最后的结果集合中。

这种算法的时间复杂度为O(N*M),其中N是第一个集合的元素个数,M是集合数目。

第二种算法

将第一个集合的所有元素都加入到一个中间集合中。

遍历后面所有的集合,对于碰到的每一个元素,从中间集合中删掉它。

最后中间集合剩下的元素就构成了差集。

这种算法的时间复杂度为O(N),其中N是所有集合的元素个数总和。

在计算差集的开始部分,会先分别估算一下两种算法预期的时间复杂度,然后选择复杂度低的算法来进行运算。还有两点需要注意:

在一定程度上优先选择第一种算法,因为它涉及到的操作比较少,只用添加,而第二种算法要先添加再删除。

如果选择了第一种算法,那么在执行该算法之前,Redis的实现中对于第二个集合之后的所有集合,按照元素个数由多到少进行了排序。这个排序有利于以更大的概率查找到元素,从而更快地结束查找。

5 总结

整数集合是集合键的底层实现之一。

整数集合以有序、无重复的方式保存集合元素。在有需要时,会根据新添加元素的类型,改变底层数组的类型。

升级操作提升了操作的灵活性,并尽可能的节约了内存。

集合可以进行交、并、差集操作。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywxwz.html