基本语法:
1)SETBIT
redis 127.0.0.1:6379> setbit KEY_NAME OFFSET VALUE //该命令用于对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。时间复杂度O(1)
1
在redis中,存储的字符串都是以二进制的形式存在的。比如:设置一个key-value,键的名字叫“andy” ,值为字符’a’,‘a’ 的ASCII码是97。转换为二进制是:01100001。offset的学名叫做“偏移” ,二进制中的每一位就是offset值,比如在这里offset 0 等于 ‘0’ ,offset 1等于’1’ ,offset2等于’1’,offset 6 等于’1’ ,没错,offset是从左往右计数的,也就是从高位往低位。
那如何通过SETBIT命令将 andy中的 ‘a’ 变成 ‘b’ 呢?即将 01100001 变成 01100010(b的ASCII码是98),其实就是将’a’中的offset 6从0变成1,将offset 7从1变成0。
这里写图片描述
每次SETBIT完毕之后,有一个(integer) 0或者(integer)1的返回值,这个是在你进行SETBIT 之前,该offset位的比特值。最后通过get andy得到的结果变成了 ‘b’ 。
2)BITCOUNT
redis 127.0.0.1:6379> bitcount andy //该命令统计字符串(字节)被设置为1的bit数
1
经过setbit操作之后,andy代表的01100010(b的ASCII码是98),共有3个1。
这里写图片描述
这里需要注意一点的是!!!!!!!!
bitcount 统计的是1的个数, bitcount test 0 -1 就是所有的, bitcount 0 0 那么就应该是第一个字节中1的数量的,注意是字节 第一个字节也就是 0 1 2 3 4 5 6 7 这八个位置上。见下面的测试样例,setbit单位是bit,bitcount是以byte为间隔统计的
3)GETBIT
redis 127.0.0.1:6379> getbit andy offset //返回key对应的string在offset处的bit值
1
4)BITOP
redis 127.0.0.1:6379> bitop operation destkey key [key...] //对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上
1
BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数:
BITOP AND destkey srckey1 … srckeyN ,对一个或多个 key 求逻辑与,并将结果保存到 destkey
BITOP OR destkey srckey1 … srckeyN,对一个或多个 key 求逻辑或,并将结果保存到 destkey
BITOP XOR destkey srckey1 … srckeyN,对一个或多个 key 求逻辑异或,并将结果保存到 destkey
BITOP NOT destkey srckey,对给定 key 求逻辑非,并将结果保存到 destkey
按位与运算符(&)
参加运算的两个数据,按二进制位进行“与”运算。
运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;
即:两位同时为“1”,结果才为“1”,否则为0
按位或运算符(|)
参加运算的两个对象,按二进制位进行“或”运算。
运算规则:0|0=0; 0|1=1; 1|0=1; 1|1=1;
即 :参加运算的两个对象只要有一个为1,其值为1。
异或运算符(^)
参加运算的两个数据,按二进制位进行“异或”运算。
运算规则:0^0=0; 0^1=1; 1^0=1; 1^1=0;
即:参加运算的两个对象,如果两个相应位为“异”(值不同),则该位结果为1,否则为0
除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入,执行结果将始终保持到destkey里面。
当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0。返回值是保存到 destkey 的字符串的长度(以字节byte为单位),和输入 key 中最长的字符串长度相等。
这里写图片描述
应用举例:
setbit 可以理解是一个位数组,至于这个数组有多大,redis中bit映射被限制在512MB之内,所以最大是2^32。也就是在这样一个位数组上存0或者是1 ,可以结合bloomFilter的应用场景理解位数组的用法,将字符串使用一种合适哈希函数映射到不同的bit位上(2^32足够的大,可以满足需求)
使用 bitmap 实现用户上线次数统计、统计活跃用户
假设现在我们希望记录自己网站上的用户的上线频率,比如说,计算用户 A 上线了多少天,用户 B 上线了多少天,诸如此类,以此作为数据,从而决定让哪些用户参加 beta 测试等活动 —— 这个模式可以使用 SETBIT 和 BITCOUNT 来实现。
比如说,通过将一个用户的id对应value上的一位,通过对活跃用户对应的位进行置位,就能够用一个value记录所有活跃用户的信息。如图bitmap有9个位被置为1,表示这9个位上对应的用户是今天的活跃用户。其中第15位表示uid为15的用户,第一位表示uid为0的用户。(如果你的uid不是从1开始的,比如从100000开始,实际上你也可以相应的用uid减去初始值来表示其位数,比如1000000用户对应到bitmap的第一位)
这里写图片描述
因为日活跃用户每天都变化,所以需要每天创建一个新的bitmap。我们简单地把日期添加到key后面,实现了这个功能。例如,要统计某一天有多少个用户至少听了一个音乐app中的一首歌曲,可以把这个bitmap的redis key设计
redis.setbit(play:yyyy-mm-dd, user_id, 1)
1
这样一次记录的复杂度是O(1),在Redis中速度非常快。而我们通过每天换用一个不同的key来将每天的活跃用户状态记录分开存。比如我们通过对3天(周一周三周四)的活跃用户记录取AND操作,就能得出这3天都活跃的用户列表。
这里写图片描述
通过上图中的测试样例可以发现:
节约空间,统计一亿人每天的登录情况,用一亿bit,约1200WByte,约10M的字符就能表示(因为bitop命令的返回值是保存到 time中的字符串的长度(以字节byte为单位),和输入 key 中最长的字符串长度相等。即1亿除以8bit=1250万Byte);
计算方便。
性能:
如果你的 bitmap 数据非常大,那么可以考虑使用以下两种方法:
● 将一个大的 bitmap 分散到不同的 key 中,作为小的 bitmap 来处理。使用 Lua 脚本可以很方便地完成这一工作。
● 使用 BITCOUNT 的 start 和 end 参数,每次只对所需的部分位进行计算,将位的累积工作(accumulating)放到客户端进行,并且对结果进行缓存 (caching)。
何时使用:
如果活跃用户在百万级别,使用Redis BitMap很划算。
如果活跃用户很少,而用户id都是10位以上的int。那就很浪费内存了,还不如使用set集合,然后求交集就可以了。