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"