Redis系列-Redis-Bitmaps-应用(JAVA-lettuce客户端)
Redis Bitmaps 简介
Bitmaps 并不是实际的数据类型,它对应的是字符串的二进制位串。可以把 Bitmaps 想象成一个以 位 为单位的数组,数组的每个单元只能存储 0 和 1,数组的下标在 Bitmaps 中叫做偏移量。
redis 限制字符串最大长度是512M,所以 Bitmaps 最大位数为 2^32。Bitmaps 的最大优势之一在存储信息时极其节约空间,假如我们的用户 id 是以唯一数字组合的,我们可以将用户的“开关”配置信息(如是否开启通知,是否接受来信,今日是否上线)保存在 Bitmaps,用户唯一数字标识代表索引,二进制 0 1 代表开关。我们存储 2^32 个用户信息,只需要 512M(谁会有这么多用户)!
操纵 Bitmaps
redis 对 Bitmaps 单独提供了一套命令(SETBIT GETBIT),所以在 Redis 中使用 Bitmaps 和使用字符串的方法不太相同。
本文中用的客户端为 lettuce https://github.com/lettuce-io/lettuce-core
getbit key-name offset 命令
将字符串当做是二进制位串(bit string),并返回位串中偏移量为 offset 的二进制位的值。
public class GetBitExample extends AbstractExample {
public static void main(String[] args) {
String str = "hello world";
//获取字符串的二进制串
String bitStr = getStringBitStr(str);
System.out.println(bitStr);
stringCommands.set("getbit", str);
for (int i = 0; i < str.length() * 8; i++) {
//一个英文字符占一个字节(Byte),一个字节等于8位(bit)
if (i % 8 == 0 && i != 0) {
System.out.print(" ");
}
//获取偏移量位置的位值
System.out.print(stringCommands.getbit("getbit", i));
}
}
}
输出结果
1101000 1100101 1101100 1101100 1101111 100000 1110111 1101111 1110010 1101100 1100100
01101000 01100101 01101100 01101100 01101111 00100000 01110111 01101111 01110010 01101100 01100100
可以看到,getbit 命令获取的正是字符对应的二进制字节中的某一偏移量的位值。
setbit key-name offset value 命令
将字符串当做是二进制位串(bit string),并将位串中偏移量为 offset 的二进制位的值设置为 value (0 或 1,不为0或1则报错)。
public class SetBitExample extends AbstractExample {
public static void main(String[] args) {
String str = "hello world";
stringCommands.set("setbit", str);
for (int i = 0; i < str.length() * 8; i++) {
//一个英文字符占一个字节(Byte),一个字节等于8位(bit)
if (i % 8 == 0 && i != 0) {
System.out.print(" ");
}
//获取偏移量位置的位值
System.out.print(stringCommands.getbit("setbit", i));
}
System.out.println();
//设置第五位值为1
stringCommands.setbit("setbit", 5, 1);
for (int i = 0; i < str.length() * 8; i++) {
//一个英文字符占一个字节(Byte),一个字节等于8位(bit)
if (i % 8 == 0 && i != 0) {
System.out.print(" ");
}
//获取偏移量位置的位值
System.out.print(stringCommands.getbit("setbit", i));
}
}
}
输出结果
01101000 01100101 01101100 01101100 01101111 00100000 01110111 01101111 01110010 01101100 01100100
01101100 01100101 01101100 01101100 01101111 00100000 01110111 01101111 01110010 01101100 01100100
bitcount key-name [start end] 命令
统计二进制位串内值为 1 的二进制位的数量,start 和 end 为可选值,给定了代表范围内统计 (注意 start 和 end 的单位是字节的范围)。
public class BitCountExample extends AbstractExample {
public static void main(String[] args) {
String str = "hello world";
stringCommands.set("bitcount", str);
for (int i = 0; i < str.length() * 8; i++) {
//一个英文字符占一个字节(Byte),一个字节等于8位(bit)
if (i % 8 == 0 && i != 0) {
System.out.print(" ");
}
//获取偏移量位置的位值
System.out.print(stringCommands.getbit("bitcount", i));
}
System.out.println();
//统计所有的字节
Long bitcount = stringCommands.bitcount("bitcount");
System.out.println(bitcount);
//统计前两个字节
bitcount = stringCommands.bitcount("bitcount", 0, 1);
System.out.println(bitcount);
}
}
输出结果
01101000 01100101 01101100 01101100 01101111 00100000 01110111 01101111 01110010 01101100 01100100
45
7
bittop op dest-key key-name [key-name …] 命令
对一个或多个二进制位串执行 op (包括 and or xor not)在内的任意一种按位运算操作,并将计算出的结果保存在 dest-key 键。
public class BitTopExample extends AbstractExample {
public static void main(String[] args) {
String str1 = "hello";
String str2 = "world";
stringCommands.set("bittop1", str1);
stringCommands.set("bittop2", str2);
printBitStr("bittop1", str1.length() * 8);
printBitStr("bittop2", str2.length() * 8);
stringCommands.bitopAnd("bittopand", "bittop1", "bittop2");
System.out.println("and");
printBitStr("bittopand", str2.length() * 8);
stringCommands.bitopOr("bittopor", "bittop1", "bittop2");
System.out.println("or");
printBitStr("bittopor", str2.length() * 8);
stringCommands.bitopXor("bittopxor", "bittop1", "bittop2");
System.out.println("xor");
printBitStr("bittopxor", str2.length() * 8);
stringCommands.bitopNot("bittopnot", "bittop1");
System.out.println("not");
printBitStr("bittopnot", str2.length() * 8);
}
private static void printBitStr(String key, int length) {
for (int i = 0; i < length; i++) {
//一个英文字符占一个字节(Byte),一个字节等于8位(bit)
if (i % 8 == 0 && i != 0) {
System.out.print(" ");
}
//获取偏移量位置的位值
System.out.print(stringCommands.getbit(key, i));
}
System.out.println();
}
}
输出结果
01101000 01100101 01101100 01101100 01101111
01110111 01101111 01110010 01101100 01100100
and
01100000 01100101 01100000 01101100 01100100
or
01111111 01101111 01111110 01101100 01101111
xor
00011111 00001010 00011110 00000000 00001011
not
10010111 10011010 10010011 10010011 10010000
应用实例
用户签到
每个用户都有自己的唯一数值对应,注意是数字类型的,因为在 bitmaps 中用户对应的是偏移量,位值 0或1 对应的是否签到。
以下为一段简单的代码模拟用户签到,查看用户是否签到,统计签到数量,用户量为 10W。
public class UserSign extends AbstractExample {
private static List<Integer> users = new ArrayList<>();
private static Random random = new Random();
static {
for (int i = 0; i < 100000; i++) {
users.add(i);
}
}
public static void main(String[] args) {
String today = DateTimeUtil.getNowDateStr(DateTimeUtil.DATE_FORMAT_YYMMDD);
String todayKey = "SIGN" + today;
System.out.println("今天是" + today);
//模拟用户签到,随机取出几个用户
int total = random.nextInt(users.size() - 1);
List<Integer> randomUsers = getUsers(total);
for (Integer randomUser : randomUsers) {
stringCommands.setbit(todayKey, randomUser, 1);
}
long begin, end;
//判断用户是否签到
begin = System.currentTimeMillis();
int i = random.nextInt(UserSign.users.size() - 1);
Integer test = UserSign.users.get(i);
Long getbit = stringCommands.getbit(todayKey, test);
System.out.println("用户 " + test + " 签到状态 " + getbit);
end = System.currentTimeMillis();
System.out.println("time " + (end - begin) + " ms");
//统计签到数
begin = System.currentTimeMillis();
Long bitcount = stringCommands.bitcount(todayKey);
System.out.println("签到数 " + bitcount + " 预测数 " + total);
end = System.currentTimeMillis();
System.out.println("time " + (end - begin) + " ms");
}
private static List<Integer> getUsers(int size) {
Set<Integer> set = new HashSet<>();
while (set.size() != size) {
int nextInt = random.nextInt(users.size() - 1);
set.add(users.get(nextInt));
}
return new ArrayList<>(set);
}
}
你们猜记录 10W 用户的签到状态用了多少内存?判断某个用户是否签到或者统计签到数用了多长时间?下面是结果:
今天是190919
签到数 18732
用户 15522 签到状态 0
time 7 ms
签到数 18732 预测数 18732
time 7 ms
10W 用户统计签到数,只用了 7ms,够快了吧。那么,看看 10W 用户的签到数据占多少内存呢?算算就知道了,就是 100000/8/1024≈12KB,够少了吧。
源码链接
https://github.com/RockeyCui/learn-normal/tree/master/learn-redis
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 rockeycui@163.com
文章标题:Redis系列-Redis-Bitmaps-应用(JAVA-lettuce客户端)
文章字数:1.7k
本文作者:崔石磊(RockeyCui)
发布时间:2019-04-07, 20:00:00
原始链接:https://cuishilei.com/redis-bitmaps-应用.html版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。