不积跬步,无以至千里;不积小流,无以成江海。
码字不易,点赞再看。
前言
欠下的债总规要还,上篇介绍基础数据结构中有提到 bitmap。移步至: 传送门
本篇先从一道面试题开始,有这么一个场景:拥有 数亿 用户的电商平台,如何统计月活??
你用 set 记录活跃用户的id,没毛病,确实也可以达到效果,但是使用这种数据结构真的妥当吗??
针对如此宝贵的内存,能省点,不香吗??
以 1亿 用户为例: 用户id 用int存储, 做个比对,引出今天的主角 bitmap。
不用我说,已经看出来优势了吧,没错这就是位图牛逼的地方。
正文
Redis官方文档对于位图的介绍如下:
位图不是一个真实的数据类型,而是定义在字符串类型上的面向 位 的操作集合。由于字符串类型是二进制安全的二进制大对象,并且最大长度是 512MB,适合于设置 2^32个不同的位。
位操作分为两组:常量时间单个位的操作,像设置一个位为 1 或者 0,或者获取该位的值。对一组位的操作,例如:在 给定的位 范围内计算 设置位 的数目。
位图的最大优势是:是一种非常显著的节省空间来存储信息的方式。例如,在一个系统中,不同用户由递增的用户 ID 来表示,可以使用 512MB 的内存来表示 40亿 用户的单个位信息(例如他们是否需要接收信件)。
简而言之,位图 是操作 比特位 的,其优点是节省 内存空间。
位图的常用操作如下:
- setbit 设置特定key对应的比特位的值。
- getbit 获取特定key对应的比特位的值。
- bitcount统计给定key对应的字符串比特位为1的数量。
Redis 的位数组是 自动扩充,如果设置了某个偏移位置超出了现有的内容范围,就会 自动 将位数组进行零扩充。
接下来我们使用 redis-cli 设置第一个字符,也就是 位数组 的前 8 位,我们只需要设置值为 1 的位,如 h (二进制:0 1 1 0 1 0 0 0) 字符只有 1/2/4 位需要设置。
127.0.0.1:6379>setbits11(integer)0127.0.0.1:6379>setbits21(integer)0127.0.0.1:6379>setbits41(integer)0127.0.0.1:6379>gets"h"
上面这个例子可以理解为「零存整取」,同样我们还也可以「零存零取」,「整存零取」。「零存」就是使用 setbit 对位值进行逐个设置,「整存」就是使用字符串一次性填充所有位数组,覆盖掉旧值。
零存零取
127.0.0.1:6379>setbita11(integer)0127.0.0.1:6379>setbita21(integer)0127.0.0.1:6379>setbita41(integer)0127.0.0.1:6379> getbita1//获取某个具体位置的值0/1(integer)1
整存零取
127.0.0.1:6379>setbhOK127.0.0.1:6379>getbitb1(integer)1127.0.0.1:6379>getbitb2(integer)1127.0.0.1:6379>getbitb3(integer)0127.0.0.1:6379>getbitb4(integer)1
如果对应位的字节是不可打印字符,redis-cli 会显示该字符的 16 进制形式。
127.0.0.1:6379>setbitx01(integer)0127.0.0.1:6379>setbitx11(integer)0127.0.0.1:6379>getx"\\xc0"
统计和查找
Redis 提供了位图统计指令 bitcount 和位图查找指令 bitpos,bitcount 用来统计指定位置范围内 1 的个数,bitpos 用来查找指定范围内出现的第一个 0 或 1。
比如我们可以通过 bitcount 统计用户一共签到了多少天,通过 bitpos 指令查找用户从哪一天开始第一次签到。如果指定了范围参数[start, end],就可以统计在某个时间范围内用户签到了多少天,用户自某天以后的哪天开始签到。
遗憾的是, start 和 end 参数是 字节索引,也就是说指定的位范围必须是 8 的倍数,这点比较坑,而不能任意指定。这很奇怪,不是很能理解 Antirez 为什么要这样设计。因为这个设计,我们无法直接计算某个月内用户签到了多少天,而必须要将这个月所覆盖的字节内容全部取出来 (getrange 可以取出字符串的子串) 然后在内存里进行统计,这个非常繁琐。
还可以将放入位图的offset统一乘以8(一个字节占8比特),这样一来就可以直接用redis的bitcount来统计对应索引范围内的比特值为1的数量了,当然这种方案的缺点也相当明显,就是浪费内存,因为原先只需要1比特存储的数据,现在需要8比特存储,所以这种方案不能很好地利用位图索引节省存储空间的特点。
接下来我们简单试用一下 bitcount 指令和 bitpos 指令:
127.0.0.1:6379>setwhelloOK127.0.0.1:6379>bitcountw(integer)21127.0.0.1:6379>bitcountw00//第一个字符中1的位数(integer)3127.0.0.1:6379>bitcountw01//前两个字符中1的位数(integer)7127.0.0.1:6379>bitposw0//第一个0位(integer)0127.0.0.1:6379>bitposw1//第一个1位(integer)1127.0.0.1:6379>bitposw111//从第二个字符算起,第一个1位(integer)9127.0.0.1:6379>bitposw122//从第三个字符算起,第一个1位(integer)17
点关注 不迷路
以上就是这篇的全部内容,能看到这里的都是 人才。
如果你从本篇内容有收获,求 点赞,求 关注,求 转发 ,让更多的人学习到。
如果本文有任何错误,请批评指教,不胜感激 !
本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.souzhinan.com/kj/295074.html