Redis-BitMap

 BitMap是什么

             经过一个bit位来表示某个元素对应的值或者状态,其中的key就是对应元素自己。Bitmaps 自己不是一种数据结构,实际上它就是字符串(key 对应的 value 就是上图中最后的一串二进制),可是它能够对字符串的位进行操做。 Bitmaps 单独提供了一套命令,因此在 Redis 中使用 Bitmaps 和使用字符串的方法不太相同。能够把 Bitmaps 想象成一个以 位 为单位的数组,数组的每一个单元只能存储 0 和 1,数组的下标在Bitmaps中叫作偏移量。redis


BitMap的命令数组

SETBIT

语法:SETBIT key offset value
bash

说明:

key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。服务器

位的设置或清除取决于 value 参数,能够是 0 也能够是 1数据结构

key 不存在时,自动生成一个新的字符串值。性能

字符串会进行伸展(grown)以确保它能够将 value 保存在指定的偏移量上。当字符串值进行伸展时,空白位置以 0 填充。测试

offset 参数必须大于或等于 0 ,小于 2^32 (bit 映射被限制在 512 MB 以内)。大数据

对使用大的 offsetSETBIT 操做来讲,内存分配可能形成 Redis 服务器被阻塞。ui

返回值:

字符串值指定偏移量上原来储存的位(bit)。spa

示例:
# SETBIT 会返回以前位的值(默认是 0)这里会生成 126 个位
coderknock> SETBIT testBit 125 1
(integer) 0
coderknock> SETBIT testBit 125 0
(integer) 1
coderknock> SETBIT testBit 125 1
(integer) 0
coderknock> GETBIT testBit 125
(integer) 1
coderknock> GETBIT testBit 100
(integer) 0
# SETBIT value 只能是 0 或者 1 二进制只能是0或者1
coderknock> SETBIT testBit 618 2
(error) ERR bit is not an integer or out of range复制代码

获取值

GETBIT


语法:GETBIT key offset
说明:

key 所储存的字符串值,获取指定偏移量上的位(bit)。

offset 比字符串值的长度大,或者 key 不存在时,返回 0

返回值:

字符串值指定偏移量上的位(bit)。

示例:
coderknock> EXISTS bit
(integer) 0
coderknock> SETBIT bit 125 1
(integer) 0
coderknock> GETBIT bit 125
(integer) 1
# 偏移量若是不存在则是0
coderknock> GETBIT bit 126
(integer) 0
# 能够看到 bit 自己也是个字符串
coderknock> GET bit
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 "复制代码

获取Bitmaps 指定范围值为 1 的位个数

BITCOUNT


语法:BITCOUNT key start
说明:

计算给定字符串中,被设置为 1 的比特位的数量。

通常状况下,给定的整个字符串都会被进行计数,经过指定额外的 startend 参数,可让计数只在特定的位上进行。

startend 参数的设置和 GETRANGE 命令相似,均可以使用负数值: 好比 -1 表示最后一个字节, -2表示倒数第二个字节,以此类推。

不存在的 key 被当成是空字符串来处理,所以对一个不存在的 key 进行 BITCOUNT 操做,结果为 0

返回值:

被设置为 1 的位的数量。

示例:
# 此处的 bit 基于 GETBIT 示例的命令中的
coderknock> BITCOUNT bit
(integer) 1
# 计算 bit 中全部值为 1 的位的个数
coderknock> BITCOUNT bit
(integer) 1
coderknock> SETBIT bit 0 1
(integer) 0
coderknock> BITCOUNT bit
(integer) 2
# 计算指定位置 bit 中全部值为 1 的位的个数
coderknock> BITCOUNT bit 10 126
(integer) 1复制代码

多个 Bitmaps 运算

BITOP


语法:BITOP operation destkey key [key ...]
说明:

对一个或多个保存二进制位的字符串 key 进行位元操做,并将结果保存到 destkey 上。

operation 能够是 ANDORNOTXOR 这四种操做中的任意一种:

  • BITOP AND destkey key [key ...] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey

  • BITOP OR destkey key [key ...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey

  • BITOP XOR destkey key [key ...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey

  • BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey

除了 NOT 操做以外,其余操做均可以接受一个或多个 key 做为输入。

处理不一样长度的字符串

BITOP 处理不一样长度的字符串时,较短的那个字符串所缺乏的部分会被看做 0

空的 key 也被看做是包含 0 的字符串序列。

返回值:

保存到 destkey 的字符串的长度,和输入 key 中最长的字符串长度相等。

示例:
coderknock> SETBIT bits-1 0 1
(integer) 0
coderknock> SETBIT bits-1 3 1
(integer) 0
# bits-1 为 1001

coderknock> SETBIT bits-2 0 1
(integer) 0
coderknock> SETBIT bits-2 1 1
(integer) 0
coderknock> SETBIT bits-2 3 1
(integer) 0
# bits-2 为 1011

#bits-1 bits-2 作 并 操做
coderknock> BITOP AND and-result bits-1 bits-2
(integer) 1
coderknock> GETBIT and-result 0
(integer) 1
coderknock> GETBIT and-result 1
(integer) 0
coderknock> GETBIT and-result 2
(integer) 0
coderknock> GETBIT and-result 3
(integer) 1
#and-result 1001

#bits-1 bits-2 作 或 操做
coderknock> BITOP OR or-result bits-1 bits-2
(integer) 1
coderknock> GETBIT and-result 0
(integer) 1
coderknock> GETBIT and-result 1
(integer) 0
coderknock> GETBIT and-result 2
(integer) 0
coderknock> GETBIT and-result 3
(integer) 1
#or-result 1011

# 非 操做只能针对一个 key
coderknock> BITOP NOT not-result bits-1 bits-2
(error) ERR BITOP NOT must be called with a single source key.
coderknock> BITOP NOT not-result bits-1
(integer) 1
coderknock> GETBIT not-result 0
(integer) 0
coderknock> GETBIT not-result 1
(integer) 1
coderknock> GETBIT not-result 2
(integer) 1
coderknock> GETBIT not-result 3
(integer) 0
# not-result 0110

# 异或操做
coderknock> BITOP XOR xor-result bits-1 bits-2
(integer) 1
coderknock> GETBIT xor-result 0
(integer) 0
coderknock> GETBIT xor-result 1
(integer) 1
coderknock> GETBIT xor-result 2
(integer) 0
coderknock> GETBIT xor-result 3
(integer) 0
# xor-result 0010复制代码

BITOP 的复杂度为 O(N) ,当处理大型矩阵(matrix)或者进行大数据量的统计时,最好将任务指派到附属节点(slave)进行,避免阻塞主节点。

计算Bitmaps中第一个值为 bit 的偏移量

BITPOS

自2.8.7可用。

时间复杂度:O(N)。

语法:BITPOS key bit [start][end]
说明:

返回字符串里面第一个被设置为 1 或者 0 的bit位。

返回一个位置,把字符串当作一个从左到右的字节数组,第一个符合条件的在位置 0,其次在位置 8,等等。

GETBITSETBIT 类似的也是操做字节位的命令。

默认状况下整个字符串都会被检索一次,只有在指定 start 和 end 参数(指定start和end位是可行的),该范围被解释为一个字节的范围,而不是一系列的位。因此start=0 而且 end=2是指前三个字节范围内查找。

注意,返回的位的位置始终是从 0 开始的,即便使用了 start 来指定了一个开始字节也是这样。

GETRANGE 命令同样,start 和 end 也能够包含负值,负值将从字符串的末尾开始计算,-1是字符串的最后一个字节,-2是倒数第二个,等等。

不存在的key将会被当作空字符串来处理。

返回值:

命令返回字符串里面第一个被设置为 1 或者 0 的 bit 位。

若是咱们在空字符串或者 0 字节的字符串里面查找 bit 为1的内容,那么结果将返回-1。

若是咱们在字符串里面查找 bit 为 0 并且字符串只包含1的值时,将返回字符串最右边的第一个空位。若是有一个字符串是三个字节的值为 0xff 的字符串,那么命令 BITPOS key 0 将会返回 24,由于 0-23 位都是1。

基本上,咱们能够把字符串当作右边有无数个 0。

然而,若是你用指定 start 和 end 范围进行查找指定值时,若是该范围内没有对应值,结果将返回 -1。

示例:
redis> SET mykey "\xff\xf0\x00"
OK
redis> BITPOS mykey 0 # 查找字符串里面bit值为0的位置
(integer) 12
redis> SET mykey "\x00\xff\xf0"
OK
redis> BITPOS mykey 1 0 # 查找字符串里面bit值为1从第0个字节开始的位置
(integer) 8
redis> BITPOS mykey 1 2 # 查找字符串里面bit值为1从第2个字节(12)开始的位置
(integer) 16
redis> set mykey "\x00\x00\x00"
OK
redis> BITPOS mykey 1 # 查找字符串里面bit值为1的位置
                    (integer) -1复制代码

BITFIELD

自3.2.0可用。

时间复杂度:每一个子命令的复杂度为 O(1) 。

语法:BITFIELD key [GET type offset][SET type offset value][INCRBY type offset increment][OVERFLOW WRAP|SAT|FAIL]
说明:

BITFIELD key GET type offset INCRBY type offset increment

`BITFIELD` 命令能够将一个 Redis 字符串看做是一个由二进制位组成的数组, 并对这个数组中储存的长度不一样的整数进行访问 (被储存的整数无需进行对齐)。 换句话说, 经过这个命令, 用户能够执行诸如 “对偏移量 1234 上的 5 位长有符号整数进行设置”、 “获取偏移量 4567 上的 31 位长无符号整数”等操做。 此外, `BITFIELD` 命令还能够对指定的整数执行加法操做和减法操做, 而且这些操做能够经过设置妥善地处理计算时出现的溢出状况。
复制代码

BITFIELD 命令能够在一次调用中同时对多个位范围进行操做: 它接受一系列待执行的操做做为参数, 并返回一个数组做为回复, 数组中的每一个元素就是对应操做的执行结果。

好比如下命令就展现了如何对位于偏移量 100 的 8 位长有符号整数执行加法操做, 并获取位于偏移量 0 上的 4 位长无符号整数:

coderknock> BITFIELD mykey INCRBY i8 100 1 GET u4 0
1) (integer) 1
2) (integer) 0复制代码

注意:

  • 使用 GET 子命令对超出字符串当前范围的二进制位进行访问(包括键不存在的状况), 超出部分的二进制位的值将被当作是 0 。

  • 使用 SET 子命令或者 INCRBY 子命令对超出字符串当前范围的二进制位进行访问将致使字符串被扩大, 被扩大的部分会使用值为 0 的二进制位进行填充。 在对字符串进行扩展时, 命令会根据字符串目前已有的最远端二进制位, 计算出执行操做所需的最小长度。

支持的子命令以及数字类型

如下是 BITFIELD 命令支持的子命令:

  • GET <type> <offset> —— 返回指定的二进制位范围。

  • SET <type> <offset> <value> —— 对指定的二进制位范围进行设置,并返回它的旧值。

  • INCRBY <type> <offset> <increment> —— 对指定的二进制位范围执行加法操做,并返回它的旧值。用户能够经过向 increment 参数传入负值来实现相应的减法操做。

除了以上三个子命令以外, 还有一个子命令, 它能够改变以后执行的 INCRBY 子命令在发生溢出状况时的行为:

  • OVERFLOW [WRAP|SAT|FAIL]

当被设置的二进制位范围值为整数时, 用户能够在类型参数的前面添加 i 来表示有符号整数, 或者使用 u 来表示无符号整数。 好比说, 咱们可使用 u8 来表示 8 位长的无符号整数, 也可使用 i16 来表示 16 位长的有符号整数。

BITFIELD 命令最大支持 64 位长的有符号整数以及 63 位长的无符号整数, 其中无符号整数的 63 位长度限制是因为 Redis 协议目前还没法返回 64 位长的无符号整数而致使的。

二进制位和位置偏移量

在二进制位范围命令中, 用户有两种方法来设置偏移量:

  • 若是用户给定的是一个没有任何前缀的数字, 那么这个数字指示的就是字符串以零为开始(zero-base)的偏移量。

  • 另外一方面, 若是用户给定的是一个带有 # 前缀的偏移量, 那么命令将使用这个偏移量与被设置的数字类型的位长度相乘, 从而计算出真正的偏移量。

好比说, 对于如下这个命令来讲:

BITFIELD mystring SET i8 #0 100 i8 #1 200
复制代码

命令会把 mystring 键里面, 第一个 i8 长度的二进制位的值设置为 100 , 并把第二个 i8 长度的二进制位的值设置为 200 。 当咱们把一个字符串键当成数组来使用, 而且数组中储存的都是同等长度的整数时, 使用 # 前缀可让咱们免去手动计算被设置二进制位所在位置的麻烦。

溢出控制

用户能够经过 OVERFLOW 命令以及如下展现的三个参数, 指定 BITFIELD 命令在执行自增或者自减操做时, 碰上向上溢出(overflow)或者向下溢出(underflow)状况时的行为:

  • WRAP : 使用回绕(wrap around)方法处理有符号整数和无符号整数的溢出状况。 对于无符号整数来讲, 回绕就像使用数值自己与可以被储存的最大无符号整数执行取模计算, 这也是 C 语言的标准行为。 对于有符号整数来讲, 上溢将致使数字从新从最小的负数开始计算, 而下溢将致使数字从新从最大的正数开始计算。 好比说, 若是咱们对一个值为 127i8 整数执行加一操做, 那么将获得结果 -128

  • SAT : 使用饱和计算(saturation arithmetic)方法处理溢出, 也便是说, 下溢计算的结果为最小的整数值, 而上溢计算的结果为最大的整数值。 举个例子, 若是咱们对一个值为 120i8 整数执行加 10计算, 那么命令的结果将为 i8 类型所能储存的最大整数值 127 。 与此相反, 若是一个针对 i8 值的计算形成了下溢, 那么这个 i8 值将被设置为 -127

  • FAIL : 在这一模式下, 命令将拒绝执行那些会致使上溢或者下溢状况出现的计算, 并向用户返回空值表示计算未被执行。

须要注意的是, OVERFLOW 子命令只会对紧随着它以后被执行的 INCRBY 命令产生效果, 这一效果将一直持续到与它一同被执行的下一个 OVERFLOW 命令为止。 在默认状况下, INCRBY 命令使用 WRAP 方式来处理溢出计算。

如下是一个使用 OVERFLOW 子命令来控制溢出行为的例子:

coderknock> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 1
2) (integer) 1

coderknock> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 2
2) (integer) 2

coderknock> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 3
2) (integer) 3

coderknock> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 0  -- 使用默认的 WRAP 方式处理溢出
2) (integer) 3  -- 使用 SAT 方式处理溢出复制代码

而如下则是一个由于 OVERFLOW FAIL 行为而致使子命令返回空值的例子:

coderknock> BITFIELD mykey OVERFLOW FAIL incrby u2 102 1
1) (nil)复制代码
做用

BITFIELD 命令的做用在于它可以将不少小的整数储存到一个长度较大的位图中, 又或者将一个很是庞大的键分割为多个较小的键来进行储存, 从而很是高效地使用内存, 使得 Redis 可以获得更多不一样的应用 —— 特别是在实时分析领域: BITFIELD 可以以指定的方式对计算溢出进行控制的能力, 使得它能够被应用于这一领域。

性能注意事项

BITFIELD 在通常状况下都是一个快速的命令, 须要注意的是, 访问一个长度较短的字符串的远端二进制位将引起一次内存分配操做, 这一操做花费的时间可能会比命令访问已有的字符串花费的时间要长。

二进制位的排列

BITFIELD 把位图第一个字节偏移量 0 上的二进制位看做是 most significant 位, 以此类推。 举个例子, 若是咱们对一个已经预先被所有设置为 0 的位图进行设置, 将它在偏移量 7 的值设置为 5 位无符号整数值 23 (二进制位为 10111 ), 那么命令将生产出如下这个位图表示:

+--------+--------+
|00000001|01110000|
+--------+--------+
复制代码

当偏移量和整数长度与字节边界进行对齐时, BITFIELD 表示二进制位的方式跟大端表示法(big endian)一致, 可是在没有对齐的状况下, 理解这些二进制位是如何进行排列也是很是重要的。

返回值:

若是 member 元素是集合的成员,返回 1

若是 member 元素不是集合的成员,或 key 不存在,返回 0

示例:
coderknock> SISMEMBER saddTest add1
(integer) 1
# add7 元素不存在
coderknock> SISMEMBER saddTest add7
(integer) 0
# key 不存在
coderknock> SISMEMBER nonSet a
(integer) 0
# key 类型不是集合
coderknock> SISMEMBER embstrKey a
(error) WRONGTYPE Operation against a key holding the wrong kind of value    复制代码

案例

使用场景一:用户签到

Jedis redis = new Jedis("192.168.31.89",6379,100000);
//用户uid
String uid = "1";
String cacheKey = "sign_"+Integer.valueOf(uid);
//记录有uid的key
// $cacheKey = sprintf("sign_%d", $uid);

//开始有签到功能的日期
String startDate = "2017-01-01";

//今天的日期
String todayDate = "2017-01-21";

//计算offset(时间搓)
long startTime = dateParase(startDate,"yyyy-MM-dd").getTime();
long todayTime = dateParase(todayDate,"yyyy-MM-dd").getTime();
long offset = (long) Math.floor((todayTime - startTime) / 86400);

System.out.println("今天是第"+offset+"天");

//签到
//一年一个用户会占用多少空间呢?大约365/8=45.625个字节,好小,有木有被惊呆?
redis.setbit(cacheKey,offset,"1");

//查询签到状况
boolean bitStatus = redis.getbit(cacheKey, offset);
//判断是否已经签到
//计算总签到次数
long qdCount = redis.bitcount(cacheKey);复制代码

使用场景二:统计活跃用户

 使用时间做为cacheKey,而后用户ID为offset,若是当日活跃过就设置为1 那么我该若是计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只有有一天在线就称为活跃),有请下一个redis的命令 命令 BITOP operation destkey key [key ...] 说明:对一个或多个保存二进制位的字符串 key 进行位元操做,并将结果保存到 destkey 上。 说明:BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操做中的任意一种参数 

Map<String,List<Integer>>dateActiveuser = new HashMap<>();
Jedis redis = new Jedis("192.168.31.89",6379,100000);
Integer[] temp01 = {1,2,3,4,5,6,7,8,9,10};
List<Integer>temp01List = new ArrayList<>();
Collections.addAll(temp01List,temp01);
dateActiveuser.put("2017-01-10",temp01List);


Integer[] temp02 = {1,2,3,4,5,6,7,8};
List<Integer>temp02List = new ArrayList<>();
Collections.addAll(temp02List,temp02);
dateActiveuser.put("2017-01-11",temp02List);

Integer[] temp03 = {1,2,3,4,5,6};
List<Integer>temp03List = new ArrayList<>();
Collections.addAll(temp03List,temp03);
dateActiveuser.put("2017-01-12",temp03List);

Integer[] temp04 = {1,4,5,6};
List<Integer>temp04List = new ArrayList<>();
Collections.addAll(temp04List,temp04);
dateActiveuser.put("2017-01-13",temp04List);

Integer[] temp05 = {1,4,5,6};
List<Integer>temp05List = new ArrayList<>();
Collections.addAll(temp05List,temp05);
dateActiveuser.put("2017-01-14",temp05List);

String date[] = {"2017-01-10","2017-01-11","2017-01-12","2017-01-13","2017-01-14"};

//测试数据放入redis中
for (int i=0;i<date.length;i++){
    for (int j=0;j<dateActiveuser.get(date[i]).size();j++){
        redis.setbit(date[i], dateActiveuser.get(date[i]).get(j), "1");
    }
}

//bitOp
redis.bitop(BitOP.AND, "stat", "stat_2017-01-10", "stat_2017-01-11","stat_2017-01-12");

System.out.println("总活跃用户:"+redis.bitcount("stat"));

redis.bitop(BitOP.AND, "stat1", "stat_2017-01-10", "stat_2017-01-11","stat_2017-01-14");
System.out.println("总活跃用户:"+redis.bitcount("stat1"));

redis.bitop(BitOP.AND, "stat2", "stat_2017-01-10", "stat_2017-01-11");
System.out.println("总活跃用户:"+redis.bitcount("stat2"));复制代码
相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息