import java.util.BitSet;
import redis.clients.jedis.Jedis;
public class SetBitTest3 {
/*
public int uniqueCount(Jedis redis,String action, String date) {
String key = action + ":" + date;
BitSet users = BitSet.valueOf(redis.get(key.getBytes()));
return users.cardinality();
}
public int uniqueCount(Jedis redis,String action, String... dates) {
BitSet all = new BitSet();
for (String date : dates) {
String key = action + ":" + date;
BitSet users = BitSet.valueOf(redis.get(key.getBytes()));
all.or(users);
}
return all.cardinality();
}*/
public static byte[] bitSet2ByteArray(BitSet bitSet) {
byte[] bytes = new byte[bitSet.size() / 8];
for (int i = 0; i < bitSet.size(); i++) {
int index = i / 8;
int offset = 7 - i % 8;
bytes[index] |= (bitSet.get(i) ? 1 : 0) << offset;
}
return bytes;
}
public static BitSet byteArray2BitSet(byte[] bytes) {
BitSet bitSet = new BitSet(bytes.length * 8);
int index = 0;
for (int i = 0; i < bytes.length; i++) {
for (int j = 7; j >= 0; j--) {
bitSet.set(index++, (bytes[i] & (1 << j)) >> j == 1 ? true
: false);
}
}
return bitSet;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Jedis j = null;
try {
j = new Jedis("192.125.155.249", 6379);
//setbit 参数说明
//setbit key 用户ID 1 or 0 (1表示用户登入过 0表示没有登入 默认为0)
// 2016-12-3 login operation user
//设置2016-12-3登入的用户ID
j.setbit("login:2016-12-3", 100, true);
j.setbit("login:2016-12-3", 101, true);
j.setbit("login:2016-12-3", 102, true);
j.setbit("login:2016-12-3", 103, true);
j.setbit("login:2016-12-3", 105, true);
//一下命令等价redis 命令 bitcount login:2016-12-3
//BitSet b = BitSet.valueOf(j.get("login:2016-12-3").getBytes());
BitSet b = byteArray2BitSet(j.get("login:2016-12-3").getBytes());
// the number of bit value 1
int lognum3 = b.cardinality();
System.out.println("2016-12-3 登入用户数量: " + lognum3);
// 2016-12-3 login operation user
//设置2016-12-4登入的用户ID
j.setbit("login:2016-12-4", 100, true);
j.setbit("login:2016-12-4", 101, true);
j.setbit("login:2016-12-4", 5852, true);
//BitSet b2 = BitSet.valueOf(j.get("login:2016-12-4".getBytes()));
BitSet b2 = byteArray2BitSet(j.get("login:2016-12-4").getBytes());
int lognum4 = b2.cardinality();
System.out.println("2016-12-4 登入用户数量: "
+ b2.cardinality());
//统计连续两天都登入的用户数
b.and(b2);
// or操作之后 同样userid的记录会重合不做记录,所以具体的数据统计看自己的需求而定
int lognumexceptsameuser = b.cardinality();
int logtotalnum = lognum3 + lognum4;
System.out
.println("2016-12-3 to 2016-12-4 login user number except same userid: "
+ lognumexceptsameuser);
System.out.println("2016-12-3 to 2016-12-4 login user number: "
+ logtotalnum);
//输出连续两天都登入的用户
System.out.println("输出连续两天都登入的用户ID ");
for (int i = b.nextSetBit(0); i >= 0; i = b.nextSetBit(i + 1)) {
System.out.print(i+"\t");
}
} catch (Exception e) {
e.printStackTrace();
}
finally {
if (j != null) {
j.disconnect();
}
}
}
}
使用位图(bitmap)
回顾上面介绍的三个方案, 我们可以得出以上结论:
- 使用有序集合或者集合能够储存具体的在线用户名单, 但是却需要消耗大量的内存;
- 而使用 HyperLogLog 虽然能够有效地减少统计在线用户所需的内存, 但是它却没办法准确地记录具体的在线用户名单。
那么是否存在一种既能够获得在线用户名单, 又可以尽量减少内存消耗的方法存在呢? 这种方法的确存在 —— 使用 Redis 的位图就可以办到。
Redis 的位图就是一个由二进制位组成的数组, 通过将数组中的每个二进制位与用户 ID 进行一一对应, 我们可以使用位图去记录每个用户是否在线。
当一个用户上线时, 我们就使用 SETBIT 命令, 将这个用户对应的二进制位设置为 1 :
# 此处的 user_id 必须为数字,因为它会被用作索引
SETBIT "online_users" <user_id> 1
通过使用 GETBIT 命令去检查一个二进制位的值是否为 1 , 我们可以知道指定的用户是否在线:
GETBIT "online_users" <user_id>
而通过 BITCOUNT 命令, 我们可以统计出位图中有多少个二进制位被设置成了 1 , 也即是有多少个用户在线:
BITCOUNT "online_users"
跟集合一样, 用户也能够对多个位图进行聚合计算 —— 通过 BITOP 命令, 用户可以对一个或多个位图执行逻辑并、逻辑或、逻辑异或或者逻辑非操作:
# 计算出 7 天都在线的用户
BITOP "AND" "7_days_both_online_users" "day_1_online_users" "day_2_online_users" ... "day_7_online_users"
# 计算出 7 在的在线用户总人数
BITOP "OR" "7_days_total_online_users" "day_1_online_users" "day_2_online_users" ... "day_7_online_users"
# 计算出两天当中只有其中一天在线的用户
BITOP "XOR" "only_one_day_online" "day_1_online_users" "day_2_online_users"