在上面的《??面试杀手锏:Redis源码之SDS??》中,我们深入分析了SDS的实现。本次介绍的位图(BitMap)是借助SDS实现的。在本文的最后,我们讲解了BitMap解决腾讯面试题的方法,并基于BitMap实现了一个模仿GitHub提交数的日历图表。我希望你会喜欢看它。1.位图介绍如果我们需要记录一个用户在一年中是否每天登录我们的系统,我们应该如何完成这个需求呢?如果使用KV存储,每个用户需要记录365条,当用户数达到数亿时,这需要的存储空间是惊人的。Redis为我们提供了位图数据结构。每个用户每天的登录记录只占一个bit,365个bit就是365天的365个bit。只需要46个字节来存储,大大节省了存储空间。位图数据结构其实并不是一个全新的东西。我们可以简单的认为它是一个数组,但是里面的内容只能是0或者1(二进制位数组)。2、命令实战Redis提供了四种常用命令SETBIT、GETBIT、BITCOUNT、BITOP,用于处理二进制位数组。SETBIT:指定位数组偏移处的二进制位设置值。偏移量从0开始计数,二进制位的值只能为0或1。返回原位置值。GETBIT:获取指定偏移处二进制位的值。BITCOUNT:统计位数组中值为1的二进制位的个数。BITOP:对多个位数组进行按位与、或、异或运算。127.0.0.1:6379>SETBIT第一个01#00000001(整数)0127.0.0.1:6379>SETBIT第一个31#00001001(整数)0127.0.0.1:6379>SETBIT第一个00#00001000(整数)1127.0.11270.1:6379>GETBIT第一个0(整数)0127.0.0.1:6379>GETBIT第一个3(整数)1127.0.0.1:6379>BITCOUNT第一个#00001000(整数)1127.0.0.1:6379>SETBIT第一个01#00001001(整数)0127.0.0.1:6379>BITCOUNT第一个#00001001(整数)2127.0.0.1:6379>SETBIT第一个11#00001011(整数)0127.0.0.1:6379>BITCOUNT第一个#00001011(整数)0.3127:6379>SETBITx31(整数)0127.0.0.1:6379>SETBITx11(整数)0127.0.0.1:6379>SETBITx01#00001011(整数)0127.0.0.1:6379>SETBITy21(整数)0127.0.0.1:6379>SETBITy11#00000110(整数)0127.0.0.1:6379>SETBITz21(整数)0127.0.0.1:6379>SETBITz01#00000101(整数)0127.0.0.1:6379>BITOPANDandResxyz#00000000(整数)1127.0.0.1:6379>BITOPORorResxyz#00001111(整数)1127.0.0.1:6379>BITOPXORxyz#00001000(执行按位取反给定的位数组127.0.0.1:6379>SETBIT值01(整数)0127.0.0.1:6379>SETBIT值31#00001001(整数)0127.0.0.1:6379>BITOPNOTnotValue值#11110110(整数)13。BitMap源码分析3.1数据结构下面展示了一个用SDS表示的一个字节(8位)长的位图:扩展:Redis中的每个对象都是一个typedef,用一个redisObject结构体来表示structredisObject{//typeunsignedtype:4;//encodingunsignedencoding:4;unsignedlru:REDIS_LRU_BITS;/*lrutime(相对于server.lruclock)*///引用计数intrefcount;//执行底层实现数据结构的指针void*ptr;}robj;type的值为REDIS_STRING,说明这是一个字符串对象sdshdr.len的值为1,说明这个SDS保存了一个1字节位数组buf[buf数组中的0。]其实保存的是位数组buf[1],buf数组中就是自动追加的\0字符。i字节,buf[i]后面的8格代表这个字节上的8位。为了再次对齐大家的思路,另外一个位数组如下:位数组由buf[0]、buf[1]、buf[2]存储,真实数据为1111000011000011101001013.2GETBITGETBIT为用于返回偏移量位数组的二进制位的值。值得注意的是,GETBIT的时间复杂度为O(1)。GETBIT命令的执行过程如下:GETBIT命令源码如下:voidgetbitCommand(client*c){robj*o;字符llbuf[32];uint64_t位偏移;size_t字节,位;size_tbitval=0;//获取偏移量if(getBitOffsetFromArgument(c,c->argv[2],&bitoffset,0,0)!=C_OK)return;//找到对应的位图对象if((o=lookupKeyReadOrReply(c,c->argv[1],shared.czero))==NULL||checkType(c,o,OBJ_STRING))return;//计算偏移量在位数组中的哪一行byte=bitoffset>>3;//计算一行中偏移的个数,相当于取模bit=7-(bitoffset&0x7);//#definesdsEncodedObject(objptr)(objptr->encoding==OBJ_ENCODING_RAW||objptr->encoding==OBJ_ENCODING_EMBSTR)if(sdsEncodedObject(o)){//SDS是RAW或EMBSTR类型if(byte
