Grape完整视频:https://segmentfault.com/a/11...intset是Redis中的一种数据结构,其地位与ziplist相同和听写。intset的定义?intset是Redis集合的底层实现之一。当加入的数据全部为整数时,使用intset;否则,将使用字典。特别地,当添加的数据是字符串,即不能用整数表示时,Redis会将数据结构转为dict,即将intset中的所有数据重定位到dict中。intset是什么意思?intset将数组中的整型元素按顺序存储,通过二分法降低了查找元素的时间复杂度。当数据量大时,依赖“搜索”的命令(如SISMEMBER)会因为O(logn)的时间复杂度而遇到一定的瓶颈,所以当数据量大时,会改用dict插图。但是intset的好处是比dict更省内存,而且在数据量小的时候,O(logn)可能不会比O(1)的hash函数慢。这就是intset存在的原因。为什么intset的内存效率更高?首先我们看一下Intset的结构:typedefstructintset{uint32_tencoding;//Intset类型编码uint32_tlength;//成员元素个数int8_tcontents[];//用于存储成员的灵活数组}intset;然后比较dict的结构:typedefstructdict{dictEntry**table;dictType*type;unsignedlongsize;unsignedlongsizemask;unsignedlongused;void*privdata;}dict;//注意contents数组成员声明为int8_t类型并不意味着内容中存储了int8_t类型成员。这个类型声明可以//被认为对内容没有意义,因为intset成员的类型完全取决于编码变量的值。encoding提供以下三个值:/*请注意,这些编码是有序的,因此:*INTSET_ENC_INT160)或前置(如果<0),*因为它在现有值的范围之外。*/if(valenc>intrev32ifbe(is->encoding)){//这种插入需要改变编码(不需要查找,因为编码改变意味着值必须插入到开头或末尾)contents)/*这总是成功的,所以我们不需要柯里化*success。*/returnintsetUpgradeAndAdd(is,value);}else{/*如果值已经存在于集合中则中止。*此调用将使用要插入的正确位置填充“pos”rt*找不到时的值。*/if(intsetSearch(is,value,&pos)){if(success)*success=0;//该值已经存在于intset中,返回失败returnis;}is=intsetResize(is,intrev32ifbe(is->length)+1);if(poslength))intsetMoveTail(is,pos,pos+1);//右移一格}_intsetSet(is,pos,value);//插入值is->length=intrev32ifbe(intrev32ifbe(is->length)+1);返回是;}/*返回所提供值所需的编码。*///根据v值的大小确定需要的编码类型staticuint8_t_intsetValueEncoding(int64_tv){if(vINT32_MAX)returnINTSET_ENC_INT64;elseif(vINT16_MAX)返回INTSET_ENC_INTUp32;否则将INTSET_ENCs}_INT1intset返回到更大的编码并插入给定的整数。*///这个函数执行的前提是value参数的大小超过当前编码//为is->content重新分配内存并修改编码以将值添加到此intsetstaticintset*intsetUpgradeAndAdd(intset*is,int64_tvalue){uint8_tcurenc=intrev32ifbe(is->encoding);//当前编码类型uint8_tnewenc=_intsetValueEncoding(value);//新编码类型intlength=intrev32ifbe(is->length);intprepend=value<0?1:0;//因为value必须超过编码限制,所以检查value是大于0还是小于0来判断value是放在content[0]还是content[length]/*首先设置new编码和调整大小*/is->encoding=intrev32ifbe(newenc);is=intsetResize(is,intrev32ifbe(is->length)+1);/*从后向前升级,这样我们就不会覆盖值。*请注意,“prepend”变量用于确保我们在intset的开头或结尾处有一个空*空格。*/while(length--)//以curenc为编码,逆序检索所有值,赋值到新位置_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));/*设置值at开始或结束。*/if(prepend)_intsetSet(is,0,value);否则_intsetSet(是,intrev32ifbe(是->长度),值);is->length=intrev32ifbe(intrev32ifbe(is->length)+1);返回是;}/*Resizetheintset*///删除is的内存分配,重新分配长度为len的intset的内存staticintset*intsetResize(intset*is,uint32_tlen){uint32_tsize=len*intrev32ifbe(is->encoding);is=zrealloc(is,sizeof(intset)+size);返回是;}//复制从index到intset末尾的整块数据到index(复制后from的值不变,但可以覆盖)staticvoidintsetMoveTail(intset*is,uint32_tfrom,uint32_tto){void*src,*dst;uint32_tbytes=intrev32ifbe(is->length)-from;uint32_tencoding=intrev32ifbe(is->encoding);if(encoding==INTSET_ENC_INT64){src=(int64_t*)is->contents+from;dst=(int64_t*)is->contents+to;字节*=sizeof(int64_t);}否则如果(encoding==INTSET_ENC_INT32){src=(int32_t*)is->contents+from;dst=(int32_t*)is->contents+to;字节*=sizeof(int32_t);}else{src=(int16_t*)is->contents+from;dst=(int16_t*)is->contents+to;字节*=sizeof(int16_t);}memmove(dst,src,bytes);}总结通过intset的底层实现,我们可以发现:基于顺序存储的整数set执行一些需要查询的命令,其时间复杂度不会像文档中说的那样是O(1)。当插入一个成员时,查询的平均时间复杂度会是O(logn),所以当整型集数据量增加时,Redis会使用dict作为集合的底层实现,降低诸如此类命令的时间复杂度作为SADD、SREM和SISMEMBER到O(1)。当然,这样会比intset消耗更多的内存。所以Redis在实现的时候,数据量小的时候会使用intset。