- intset整数有序集合简介
Redis中intset可以用来存储无重复的整数有序集合, 并且可以根据元素的值来选择使用什么整数类型来进行保存,可选择的包括int16_t,int32_t,int64_t,分别为2/4/8字节的整数类型。同时因为经常涉及到不同整数类型之间的转换,所以通常存取都是按单字节来进行的,这里就会涉及到一个大端小端模式的问题。
Redis中intset的整数类型只能进行升级不能进行降级,因此存在一定的内存浪费。
redis 尽量把所有的多字节类型按照小端编码(但是有些场景要向后兼容,仍然是大端),是因为大多数生产环境是小端;在内存中ziplist、intset、zipmap在内存中是endin-中性的(what is nuetral)场景,需要很多转换操作,因为在写磁盘持久化的时候直接系统调动write(2),而没有其他步骤。所以在很多操作时要显式地进行大小端的转换。
- Redis中的大小端转换
在源码中实现大小端转换的主要是endianconv.c和endianconv.h文件,只有在宿主机是大端的情况下,定义的宏才真正有效,小端情况无需操作(因为redis也是以小端为准的读取字节所以不需要调整)。XXXXifbe方法是其他文件调用endianrev的方式。包括memrevXXifbe/intrevXXifbe,其中XX则表示要转换的是16/32/64中的一种。其中memrevXXifbe函数形参为指针,intrevXXifbe形参为一个整数,内部实现相同,都是为了让一个无符号整数按位逆序。下面给出其中一个实例:
#if (BYTE_ORDER == LITTLE_ENDIAN)
#define memrev16ifbe(p)
#define memrev32ifbe(p)
#define memrev64ifbe(p)
#define intrev16ifbe(v) (v)
#define intrev32ifbe(v) (v)
#define intrev64ifbe(v) (v)
#else
#define memrev16ifbe(p) memrev16(p)
#define memrev32ifbe(p) memrev32(p)
#define memrev64ifbe(p) memrev64(p)
#define intrev16ifbe(v) intrev16(v)
#define intrev32ifbe(v) intrev32(v)
#define intrev64ifbe(v) intrev64(v)
#endif
//memrevXXifbe/intrevXXifbe实际调用的函数,还有16和64版本,根据整数类型不同调用不同版本
//当然如果主机本身是小端模式,那么memrevXXifbe/intrevXXifbe不会做任何操作
void memrev32(void *p) {
unsigned char *x = p, t;
t = x[0];
x[0] = x[3];
x[3] = t;
t = x[1];
x[1] = x[2];
x[2] = t;
}
- intset整数有序集合具体实现
因为intset自己根据值类型来存储实际整数,自然就需要有一个变量来表示当前存储的整数类型具体是什么,encoding变量有三种取值,即2/4/8字节 。
注意对于编码变量和元素数量这两个成员变量本身使用4字节整数变量存储的,而实际数据内容是用单字节整数来存取的。
typedef struct intset {
uint32_t encoding; // 编码方式 所使用的类型的长度2/4/8字节
uint32_t length; // 集合包含的元素数量
int8_t contents[]; // 保存元素的数组
} intset;
那么intset是如何添加元素的呢?
intsetAdd函数添加一个元素value时,首先根据value的字节数与当前intset的encoding进行比较,分析intset是否需要升级,若需要升级则调用intsetUpdateAndAdd函数处理,否则如果value已存在intset中直接pass,不存在,那么先resize,接着将插入位置之后的所有元素向后偏移,添加value。