HyperLogLog
(1)采用一种基数算法,用于完成独立总数的统计。
(2)占据空间小,无论统计多少个数据,只占12k的内存空间。
(3)不精确的统计算法,标准误差为0.81%。
Bitmap
(1)不是一种独立的数据结构,实际上就是字符串。
(2)支持按位存取数据,可以将其看成是byte数组。
(3)适合存储索大量的连续的数据的布尔值。
// 统计20万个重复数据的独立总数.
@Test
public void testHyperLogLog() {
String redisKey = "test:hll:01"; //HyperLogLog
//往数据中存10万个不重复的数据
for (int i = 1; i <= 100000; i++) {
redisTemplate.opsForHyperLogLog().add(redisKey, i);
}
//往数据中存10万个有重复的数据 random->重复
for (int i = 1; i <= 100000; i++) {
int r = (int) (Math.random() * 100000 + 1);
redisTemplate.opsForHyperLogLog().add(redisKey, r);
}
//这样就存了20万行数据,有重复的,那么,去重复之后应该是10万个独立的数据
long size = redisTemplate.opsForHyperLogLog().size(redisKey);
//统计结果:99553,误差差不多0.8%左右
System.out.println(size);
}
//比如统计一周7天的uv,合并数据
// 将3组数据合并, 再统计合并后的重复数据的独立总数.
//多组数据可能有重复的,这个统计的是合并之后的重复数据之中,独立的数据总数
@Test
public void testHyperLogLogUnion() {
String redisKey2 = "test:hll:02";
//10000条非重复的数据
for (int i = 1; i <= 10000; i++) {
redisTemplate.opsForHyperLogLog().add(redisKey2, i);
}
String redisKey3 = "test:hll:03";
//10000条非重复的数据
for (int i = 5001; i <= 15000; i++) {
redisTemplate.opsForHyperLogLog().add(redisKey3, i);
}
String redisKey4 = "test:hll:04";
//10000条非重复的数据
for (int i = 10001; i <= 20000; i++) {
redisTemplate.opsForHyperLogLog().add(redisKey4, i);
}
//那么合并之后,一共有30000条数据,它们之间是有交叉重复的,但是独立的数据(也就是去重后)理论上有20000条
//合并之后产生新的数据"test:hll:union"
String unionKey = "test:hll:union";
redisTemplate.opsForHyperLogLog().union(unionKey, redisKey2, redisKey3, redisKey4);
long size = redisTemplate.opsForHyperLogLog().size(unionKey);
//结果19833,误差还行。理论20000条
System.out.println(size);
}
//bitmap:本质上是string,只不过是特殊的操作,按位存
// 统计一组数据的布尔值
@Test
public void testBitMap() {
String redisKey = "test:bm:01";
// 记录
redisTemplate.opsForValue().setBit(redisKey, 1, true);
redisTemplate.opsForValue().setBit(redisKey, 4, true);
redisTemplate.opsForValue().setBit(redisKey, 7, true);
// 查询
//默认是false
System.out.println(redisTemplate.opsForValue().getBit(redisKey, 0));
System.out.println(redisTemplate.opsForValue().getBit(redisKey, 1));
System.out.println(redisTemplate.opsForValue().getBit(redisKey, 2));
// 统计
//执行redis命令,做匿名的实现
Object obj = redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
//按位统计,会帮我们统计这组byte中true的个数
return connection.bitCount(redisKey.getBytes());
}
});
//结果:3个,没错!
System.out.println(obj);
}
// 统计3组数据的布尔值, 并对这3组数据做OR(与或非)运算.
@Test
public void testBitMapOperation() {
String redisKey2 = "test:bm:02";
//其他的位置就默认是false
redisTemplate.opsForValue().setBit(redisKey2, 0, true);
redisTemplate.opsForValue().setBit(redisKey2, 1, true);
redisTemplate.opsForValue().setBit(redisKey2, 2, true);
String redisKey3 = "test:bm:03";
redisTemplate.opsForValue().setBit(redisKey3, 2, true);
redisTemplate.opsForValue().setBit(redisKey3, 3, true);
redisTemplate.opsForValue().setBit(redisKey3, 4, true);
String redisKey4 = "test:bm:04";
redisTemplate.opsForValue().setBit(redisKey4, 4, true);
redisTemplate.opsForValue().setBit(redisKey4, 5, true);
redisTemplate.opsForValue().setBit(redisKey4, 6, true);
//做 or 运算
String redisKey = "test:bm:or";
//得到结果
Object obj = redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
//做运算 bitOp:bitOperation
connection.bitOp(RedisStringCommands.BitOperation.OR,/*运算符*/
redisKey.getBytes()/*结果存到这里面*/,
/*要运算的数据*/
redisKey2.getBytes(), redisKey3.getBytes(), redisKey4.getBytes());
return connection.bitCount(redisKey.getBytes());
}
});
//做or运算false true -> true
System.out.println(obj);
//right
System.out.println(redisTemplate.opsForValue().getBit(redisKey, 0));
System.out.println(redisTemplate.opsForValue().getBit(redisKey, 1));
System.out.println(redisTemplate.opsForValue().getBit(redisKey, 2));
System.out.println(redisTemplate.opsForValue().getBit(redisKey, 3));
System.out.println(redisTemplate.opsForValue().getBit(redisKey, 4));
System.out.println(redisTemplate.opsForValue().getBit(redisKey, 5));
System.out.println(redisTemplate.opsForValue().getBit(redisKey, 6));
}