什么是IntSet
IntSet基于整数数组实现,并且长度可变,数组有序,是Set集合的一种实现方式;用来存储无重复的整数有序集合,并且可以根据元素的值,统一更改编码格式(但只能小变大);也就是整数数组存储的几个编码格式;分别是16位(int16_t)、32位 (int32_t)、64位 (int64_t);分别对应2、4、8字节的整数类型,也就是java中short、int、long整数类型;
IntSet结构
typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;
encoding便是contents数组的编码方式;分别有(INTSET_ENC_INT16、INTSET_ENCINT_32 和 INTSET_ENC_INT64)
length表示IntSet包含的元素的数量;
IntSet结构图示
一个存储了{5,15,25}的IntSet结构图示大概这样
encoding: INTSET_ENC_INT16 | length: 3 | 5 | 15 | 25 |
头部分:8个字节 (encoding:4字节、length:4字节)
数组部分:3 * 2字节 = 6 字节
为什么编码一致
为什么编码一致?编码一致,不是会导致内存浪费吗?
其实细想就会知道,编码一致会避免很多不必要的麻烦,其中还有很重要的一点就是:编码一致之后,查找速度(寻址)就快了很多。计算指针的偏移量后就可以知道该元素的存储位置。
比如一个2字节编码数组的第三个元素,角标就是2,那么偏移量就是2*2=4个字节,从这个地址开始两个字节就是第三个元素;
所以该元素内存地址就是:数组起始地址加偏移量
如何实现长度可变和数组有序?
添加元素
intsetAdd函数添加一个元素value时,首先根据value的字节数与当前intset的encoding进行比较,分析intset是否需要升级,若需要升级则调用intsetUpdateAndAdd函数处理,否则如果value已存在intset中直接pass,不存在,那么先resize,接着将插入位置之后的所有元素向后偏移,添加value。
动态升级
当插入的元素字节数超过了原数组encoding范围时,就会调用intsetUpdateAndAdd进行升级。
假设当前存储{5,15,25}三个2字节,现要添加数49999,已经超过int16_t的范围了所以就需要进行编码升级。升级过程如下:
encoding: INTSET_ENC_INT16 | length: 3 | 5 | 15 | 25 |
4字节 | 4字节 | 2字节 | 2字节 | 2字节 |
统一编码为4个字节:按照4字节大小和元素个数扩容数组,申请内存空间;
倒叙依次将原数组拷贝到扩容后正确的位置(倒叙是为了避免覆盖);
将新增元素添加到数组末尾(因为进行了编码升级,所以要么比原数组都大,要么比原数组都小);
最后更改头部encoding和length;
encoding: INTSET_ENC_INT32 | length: 4 | 5 | 15 | 25 | 49999 |
4字节 | 4字节 | 4字节 | 4字节 | 4字节 | 4字节 |