Redis的bitMap命令
bitmaps的基本命令都在string的的命令当中。
因为redis的key和value本身就支持二进制的存储方式,所以bitmaps只是一个独特的扩展。因为是面向字节操作,所以他的最大长度就是512M,最适合设置成2^32个不同字节。
一般的使用场景都是单一的统计,效率较高。比如签到啊,比如点击啊。有人可能会说用set也可以做出类似操作,的确,set就有类似功能,但是不同的实现会有不同的侧重点,
此就是相对于map效率高了很多。本身的存储方式就决定了他的效率。
2.Bitmap相关命令
(1)
命令:SETBIT key offset value
时间复杂度:O(1)
命令描述:对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。位的设置或清除取决于 value 参数,可以是 0 也可以是 1 。当 key 不存在时,自动生成一个新的字符串值。字符串会进行伸展(grown)以确保它可以将 value 保存在指定的偏移量上。当字符串值进行伸展时,空白位置以 0 填充。offset 参数必须大于或等于 0 ,小于 2^32 (bit 映射被限制在 512 MB 之内)。
返回值:指定偏移量原来储存的位。
(2)
命令:GETBIT key offset
时间复杂度:O(1)
命令描述:对 key 所储存的字符串值,获取指定偏移量上的位(bit)。当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。
返回值:字符串值指定偏移量上的位(bit)。
(3)
命令:BITCOUNT key [start] [end]
时间复杂度:O(N)
命令描述:计算给定字符串中,被设置为 1 的比特位的数量。一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推。不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。
返回值:被设置为 1 的位的数量。
(4)
命令:BITOP operation destkey key [key ...]
时间复杂度:O(N)
命令描述:对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种:
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 的字符串序列。
当处理大型矩阵(matrix)或者进行大数据量的统计时,最好将任务指派到附属节点(slave)进行,避免阻塞主节点。
返回值:保存到 destkey 的字符串的长度,和输入 key 中最长的字符串长度相等。
ps:
第一个,就是bitcount命令,在使用start,end的时候一定要注意,setbit和getbit命令操作的是bit,但是bitcount用的是byte来计算位数,两者差了8倍,因此这点很容易采坑,也不建议用。
setbit的offset是用大小限制的,在0到 232(最大使用512M内存)之间,即0~4294967296之前,超过这个数会自动将offset转化为0,因此使用的时候一定要注意。
import com.alibaba.fastjson.JSONObject;
import org.junit.Test;
import redis.clients.jedis.BitOP;
import redis.clients.jedis.BitPosParams;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
/** BitMapTes
*/
public class BitMapTest {
//记录签到的开始时间:20180101
private static final long dateStart = 1514736000000L;
public BitMapTest() throws ParseException {}
/**
* 假设用户ID是从1开始递增的LONG类型,那么可以用1个bitMap表示网站用户的当天的签到情况
*/
@Test
public void test1() {
Redis redis = RedisFactory.get();
int[] userId = new int[]{1, 5, 9, 111, 232, 434, 566, 121, 12123};
for (int i = 0; i < userId.length; i++) {
redis.setbit("user_sign_20180101", userId[i], true);
}
redis.setbit("user_sign", 8839, "1");
System.out.println("20180101 签到用户数:" + redis.bitcount("user_sign"));
for (int i = 0; i < userId.length; i++) {
redis.setbit("user_sign_20180102", userId[i], true);
}
for (int i = 0; i < userId.length; i++) {
redis.setbit("user_sign_20180103", userId[i], true);
}
for (int i = 0; i < userId.length; i++) {
redis.setbit("user_sign_20180105", userId[i], true);
}
for (int i = 0; i < userId.length; i++) {
redis.setbit("user_sign_20180106", userId[i], true);
}
for (int i = 0; i < userId.length; i++) {
redis.setbit("user_sign_20180107", userId[i], true);
}
for (int i = 0; i < userId.length; i++) {
redis.setbit("user_sign_20180126", userId[i], true);
}
for (int i = 0; i < userId.length; i++) {
redis.setbit("user_sign_20180206", userId[i], true);
}
//求20180206的总签到数
System.out.println(redis.bitcount("user_sign_20180206"));
//求用户111在20180206的签到情况
System.out.println("user_111_sign_20180206:" + redis.getbit("user_sign_20180206", 111));
//求用户111在201801的签到列表
LinkedHashMap user_111_201801 = new LinkedHashMap<String, Integer>();
for (int i = 20180101; i <= 20180131; i++) {
user_111_201801.put("" + i, redis.getbit("user_sign_" + i, 111) ? 1 : 0);
}
System.out.println(JSONObject.toJSONString(user_111_201801));
//求用户111在201801月份的连续签到天数
List<String> list = new ArrayList<>();
TreeMap<Integer, List<String>> map = new TreeMap<>();
for (int i = 20180101; i <= 20180131; i++) {
if (redis.getbit("user_sign_" + i, 111)) {
list.add("" + i);
}
if (!redis.getbit("user_sign_" + i, 111)) {
if (list.size() > 0) {
map.put(list.size(), list);
list = new ArrayList<>();
}
}
}
map.put(list.size(), list);
Map.Entry<Integer, List<String>> maxEntry = map.lastEntry();
System.out.println("userid:111,最大签到天数:" + maxEntry.getKey() + ",签到列表:" + maxEntry.getValue().toString());
}
/**
* 用户ID不是从1递增的情况,用1个位图表示用户每天的签到情况
*/
@Test
public void test2() {
Redis redis = RedisFactory.get();
String[] userIds = new String[]{"ds0001a", "ds0003b", "ds0004c", "ds0005d"};
for (int i = 0; i < userIds.length; i++) {
redis.setbit("user_sign_" + userIds[i], getOffset("20180101"), true);
redis.setbit("user_sign_" + userIds[i], getOffset("20180102"), true);
redis.setbit("user_sign_" + userIds[i], getOffset("20180103"), true);
redis.setbit("user_sign_" + userIds[i], getOffset("20180105"), true);
redis.setbit("user_sign_" + userIds[i], getOffset("20180126"), true);
redis.setbit("user_sign_" + userIds[i], getOffset("20180206"), true);
}
//求20180206的总签到数,实现比较困难,用hyperloglog做粗略统计,或者用其他数据结构
//求用户ds0003b在20180206的签到情况
System.out.println("user_ds0003b_sign_20180206:" + redis.getbit("user_sign_ds0003b", getOffset("20180206")));
//求用户user_sign_ds0003b在201801的签到列表
LinkedHashMap user_sign_ds0003b_201801 = new LinkedHashMap<String, Integer>();
long soffset = getOffset("20180101");
for (int i = 0; i < 31; i++) {
user_sign_ds0003b_201801.put("201801" + (1 + i), redis.getbit("user_sign_ds0003b", soffset + i) ? 1 : 0);
}
System.out.println(JSONObject.toJSONString(user_sign_ds0003b_201801));
//求用户111在201801月份的连续签到天数
// List<String> list = new ArrayList<>();
// for (int i = 20180101; i <= 20180131; i++) {
// for (int j = i; j <= 20180131; j++) {
// if (i == j) {
// if (redis.getbit("user_sign_ds0003b", getOffset("" + i))) {
// System.out.println(1);
// } else {
// break;
// }
// continue;
// }
//
//第一个,就是bitcount命令,在使用start,end的时候一定要注意,setbit和getbit命令操作的是bit,但是bitcount用的是byte来计算位数,两者差了8倍,因此这点很容易采坑,也不建议用。
// Long user_sign_ds0003b = redis.bitcount("user_sign_ds0003b", getOffset("" + i) + 1, getOffset("" + j) + 1);
// if (user_sign_ds0003b == j - i + 1) {
// if (j == 20180131) {
// list.add((j - i + 1) + "_" + i + ":" + j);
// }
// } else {
// if (j > i && user_sign_ds0003b > 0) {
// list.add((j - i) + "_" + i + ":" + (j - 1));
// }
// break;
// }
// }
// }
// System.out.println(list.toString());
System.out.println(redis.bitpos("user_sign_ds0003b", true));
System.out.println(redis.bitpos("user_sign_ds0003b", true, new BitPosParams(1)));
//又是坑,表示1*8位后第一个为true的位置
System.out.println(redis.bitfield("user_sign_ds0003b"));
System.out.println(redis.bitop(BitOP.AND, "new_key", "user_sign_ds0003b", "user_sign_ds0001a"));//位运算
}
private long getOffset(String ymd) {
try {
return (new SimpleDateFormat("yyyyMMdd").parse(ymd).getTime() - dateStart) / (1000 * 60 * 60 * 24);
} catch (ParseException e) {
e.printStackTrace();
return -1;
}
}
@Test
public void test3() throws ParseException {
System.out.println(new SimpleDateFormat("yyyyMMdd").parse("20180101").getTime());
System.out.println(new SimpleDateFormat("yyyyMMdd").parse("20180102").getTime());
System.out.println((new SimpleDateFormat("yyyyMMdd").parse("20180102").getTime() - new SimpleDateFormat("yyyyMMdd").parse("20180101").getTime()) / 1000 / 60 / 60 / 24);
// System.out.println(getOffset("20180102"));
}
//5 GETBIT key offset 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
//7 SETBIT key offset value 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
}
- 使用Redis bitmap统计活跃用户
- Redis BitMap 统计用户活跃指标
- MySQL 命令大全
- 获取Redis里的所有健值对
- spring集成Redis各种模式 单Redis,Sentinel 哨兵模式,Redis Cluster集群,Redis Sharding集群
- 批量删除redis中以某字符串前缀的key
- Redis 冒号分隔符
- redis连接方式推荐使用
- Redis实现分布式限流
- Redis实现分布式锁
- Spring+redis实现session集群
- Redis实现分布式锁
- Redis 统计7天连续在线用户人数
- Redis+Lua实现分布式限流
- spring-data-redis与Jedis整合配置