1、什么是Bitmap?
就是通过一个bit位来表示某个元素对应的值或者状态,其中的key就是对应元素本身。我们知道8个bit可以组成一个Byte,所以Bitmap本身会极大的节省储存空间。
2、Bitmap在Redis中的基本含义
我们先来看一个简单的Redis对于setbit命令的解释说明
这个offset所指的偏移量是二进制位的偏移量,而不是字节索引的偏移量
我们知道一个字节占用8位,所以Redis面向字节的索引1应当对应于面向二进制的索引0~7,面向字节的索引2应当对应于面向二进制的索引8~15,以此类推。字节在内存中是一个一个割裂开来的,但是二进制不是,二进制就是一长串的连续。
举个例子,我们执行一个基础的Bitmap命令:setbit k1 1 1
当执行setbit k1 1 1这个命令的时候,k1是key的名字这个不用多说,第一个1指的是二进制中从左向右偏移1位,第二个1指的是值设为1,也就是创建了01000000这个值。那么,k1用strlen获取的长度是多少?值又是什么?
首先我们要知道,strlen获取的是字节长度,刚刚已经说了,在内存中创建了01000000这个值,正如上文所说,面向字节的索引1应当对应于面向二进制的索引0~7,offset偏移量1在0~7范围内,所以01000000只操纵了一个字节,所以strlen获取的长度是1。假设执行setbit k2 10 1,那么就是创建了 00000000 00100000这个值,此时就需要两个字节来存储,那么strlen k2就是2了。
而获取k1值的时候,get k1的时候,会将01000000转为十进制,也就是64,对应ASCII码中的“@”(Redis默认按照ASCII码表解码),所以get k1的值就是@。
同样的,我们再来看 setbit k1 7 1,请问这个时候k1用strlen获取的长度是多少?值又是什么?
没错,k1的长度还是1个字节,因为7代表的是第8个二进制位(从0开始),还没超过1个字节,所以不用开辟新的字节,所以strlen长度还是1个字节。而get出来的数值就是将01000001转换为十进制65,对应的ASCII码值就是A。
我们现在把offset的偏移量设大一点,比如setbit k1 9 1,请问这个时候k1用strlen获取的长度是多少?值又是什么?
没错,此时k1的长度就是2个字节了,因为9已经超过了1个字节的长度了。那么k1的值是多少?是A@。因为Redis在读取值的时候是按照一个字节一个字节读取出来的,setbit k1 9 1,得到了这么两个二进制数:01000001 01000000,那么相应读取出来的就是两个字节的值。
2.1补充一些编码集的小常识
字符集标准的是ASCII字符集,其他的一般都叫扩展字符集。什么叫扩展?就是其他字符集不再对ASCII重编码,而是复用。ASCII有个规律,就是必须以0开始:0xxxxxxxxx……那么在字节流读取的时候,肯定是每个字节判断,只要这个字节是0开头,直接去和ASCII码进行比对就好。但是一旦不是0开头,比如UTF-8里面,3个1,1个0的话,那么除了这个字节,还要再读出2个字节,一共用3个字节来表示。把它的前端码替换掉之后,再用剩余的二进制位拼成一个数值,去客户端的字符集中比对显示。
3、Bitmap的基本使用
搞清楚了Bitmap在Redis中的含义以后,以及初步了解了setbit的基本用法之后,我们再来看几个常用的关于Bitmap命令。
3.1、bitpos
根据Reids的说明,用来寻找二进制位的地址,我们再用刚刚上面的k1来做演示。
bitpos k1 1 0 0,意思就是在1~1个字节间查询1的位置,k1的二进制值是:01000001 01000000,也就是在01000001里面查询1的位置,返回1(从0开始,1表示是第2位)。
那如果我查第二个字节里面的偏移量呢?bitpos k1 1 1 1,此时返回的应该是多少?还是1吗?不是,是9。虽然查询的时候范围1 1指的是查询第二个字节里面1的位置,但是返回的时候却是按照整个二进制从左往右的位置,所以第2个字节中的1在整个二进制位中是第10个位置。
那如果我在1、2两个字节里面去找呢?这时返回的还是1,因为找的是第一次出现的位置。
3.2、bitcount
根据Reids的说明,用来统计1在范围区间内出现了几次,我们再用刚刚上面的k1来做演示。
在1、2两个字节中,统计1出现的次数,很简单,在01000001 01000000中,1共出现了3次。
3.3、bitop
根据Reids的说明,用来将多个Key的值进行二进制位的与或非操作,得到的值存入目标Key中。
先准备一些演示数据:设置k1为A(01000001),设置k2为B(01000010)。
先做“与”运算(有0则0,全1为1),看看是什么:bitop and andKey k1 k2,意思是将k1的值和k2的值进行按位与操作,结果存入andKey中,那么get andKey中的值是多少?是@。
再做“或”运算(有1则1,全0为0),看看是什么:bitop or orKey k1 k2,意思是将k1的值和k2的值进行按位或操作,结果存入orKey中,那么get orKey中的值是多少?没错,就是C。
4、Bitmap实际案例
我相信有些朋友或许是第一次听说过Redis中的Bitmap,完全不明白这个Bitmap到底有什么用?其实这个Bitmap用处可大了,因为这是最底层的二进制直接计算,效率极快。我们可以通过几个例子来看看Bitmap的实际应用。
4.1、假设在一家电商公司里面,有这么个需求:要求统计某个时间段内用户的登录天数,怎么处理?
按照传统的设计思路,我们可以用MySQL来记录用户的登录,用户的每次登录都将在MySQL表中产生一行记录。但是MySQL是关系型数据库,表与表之间必然存在主外键的关联,这个关联的ID就得占用好几个字节了,姑且算4个字节吧,还有日期再占用4个字节吧,那一笔登录至少就得占用8个字节(还没算其他的记录信息,先暂且忽略吧)。那么对于体量超大的公司,用户可能天天登录,存储和查询的成本就非常高。如何优化?上大数据?没必要吧,Redis足矣。
首先在这个需求里面,有两个固定的数值:
① 一年有365/366天,我给你个整数,我当你有400天,够大方了吧。如果每天对应一个二进制位,从左向右,第1个二进制位代表第1天,第2个二进制位代表第2天……一年也就占用400个不到的二进制位,还用不到50个字节就可以记录用户全年每天的登录状态。
按照上图所示,feenix这个用户就在第1天,第3天,第360天登录。
也就占用45个字节。
现在我要知道feenix这个用户最后两周有没有登录,怎么查?
-2和-1是Redis的字节索引,Redis面向字节有正索引和负索引,正索引从0开始,负索引从-1开始。图中-2 -1指的就是最后的两个字节,1个字节8位,也就是查询了一年中最后的16天用户的登录次数。
4.2、还是刚刚那家电商公司里面,又来了这么个需求:在双十一活动这天,只要用户登录就送礼物(假设有2亿用户),那么仓库里面该准备多少个礼物?
其实在电商的用户一般分为僵尸用户、冷热用户和忠诚用户,所以在这2亿用户里面,关键在于活跃用户的统计,怎么统计?假设以天为单位,不管用户登录了多少次,就算活跃了1次,所以每天对于用户的登录都需要去重。
回到Redis中的Bitmap,再上一个需求中,Key是用户,Value标记是否登录。在这个需求中,将Key和Value颠倒一下,Key是日期,Value是用户,就可以记录每天有多少用户登录。Value怎么做用户,Value不是只能标记0和1吗?可以这么想,可以建立一张映射表,将所有的用户一字排开,分别映射到每一个二进制位上。
这样就可以将用户每天是否登录标记出来,最后用bitop的按位或操作(or)来统计在给定的时间范围内有多少个用户登录过。图中0 -1指的是统计所有的用户,也可以根据需求来指定范围内的目标用户。
5、Bitmap在实际使用中的意义
要知道,位图的运用绝不仅仅是节省空间,二进制位的操作在计算机系统中CPU计算速度是最快的。如果换成关系型数据库的话,读磁盘就会产生磁盘IO,读取数据之后还要做数据的字符解码操作,在参与一些非二进制的计算,效率就大打折扣了。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/111961.html