项目1在线交流平台-7.构建安全高效的企业服务-4.Redis两种高级数据结构-HyperLogLog、BItMap


参考牛客网高级项目教程
狂神说Redis教程笔记

功能需求

在这里插入图片描述

  • 为了对网站运营数据进行统计,并节约成本,可以使用redis的两种高级数据结构
  • 认识和测试redis的两种高级数据结构

一、HyperLoglog-基数统计

1. HyperlogLog数据类型特点

  • Redis HyperLogLog 是用来做基数统计的算法,用以完成独立总数的统计
  • HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的
    • 并且是很小的。花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。
  • 因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身
    • 所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
  • 其底层使用string数据类型
  • 其是不精确的统计算法,标准误差为0.81%
什么是基数
  • 数据集中不重复的元素的个数。

2. 应用场景

  • 网页的访问量(UV):一个用户多次访问,也只能算作一个人

传统实现,存储用户的id,然后每次进行比较。当用户变多之后这种方式及其浪费空间,而我们的目的只是计数,Hyperloglog就能帮助我们利用最小的空间完成。

  • 即如果允许容错或对精度要求没有这么高,那么一定可以使用Hyperloglog !
  • 如果不允许容错,就使用set或者自己的数据类型即可 !

3. 常用API及使用Spring客户端测试

redis原生API
命令描述
pfadd key element1 [elememt2..]添加指定元素到 HyperLogLog 中
pfcount key [key]返回给定 HyperLogLog 的基数估算值。
pfmerge mergekey sourcekey [sourcekey..]将多个 HyperLogLog 合并为一个 HyperLogLog
# 添加元素和统计个数
127.0.0.1:6379> PFADD myelemx a b c d e f g h i j k # 添加元素
(integer) 1
127.0.0.1:6379> type myelemx # hyperloglog底层使用String
string
127.0.0.1:6379> PFCOUNT myelemx # 估算myelemx的基数
(integer) 11
127.0.0.1:6379> PFADD myelemy i j k z m c b v p q s
(integer) 1
127.0.0.1:6379> PFCOUNT myelemy
(integer) 11

# 合并
127.0.0.1:6379> PFMERGE myelemz myelemx myelemy # 合并myelemx和myelemy 成为myelemz
OK
127.0.0.1:6379> PFCOUNT myelemz # 估算基数
(integer) 17
Spring客户端操作API
opsForHyperLogLog().add(pfKey, i)-添加数据
opsForHyperLogLog().size(pfKey)-统计数据的基数
/**
 * 测试对HyperlogLog的操作
 */
@Test
public void testHyperLogLog() {
    // 添加100 000个不重复的数、100 000个重复的数-共20万个数
    String pfKey = "test:hll:01";
    for (int i = 0; i < 100000; i++) {
        redisTemplate.opsForHyperLogLog().add(pfKey, i);
    }
    for (int i = 0; i < 100000; i++) {
        int r = (int)(Math.random() * 100000);
        redisTemplate.opsForHyperLogLog().add(pfKey, r);
    }
    // 统计指定key中所有不重复的基数个数
    long size = redisTemplate.opsForHyperLogLog().size(pfKey);
    System.out.println(size);
}
99565
opsForHyperLogLog().union(unionKey, pfKey2, pfKey3, pfKey4)
  • 将pfKey2, pfKey3, pfKey4的数合并到unionKey中
/**
 * 合并数据-并统计合并后的基数
 */
@Test
public void testHyperLogLogUnion() {
    String pfKey2 = "test:hll:02";
    String pfKey3 = "test:hll:03";
    String pfKey4 = "test:hll:04";
    for (int i = 0; i < 10000; i++) {
        redisTemplate.opsForHyperLogLog().add(pfKey2, i);
    }
    for (int i = 5000; i < 15000; i++) {
        redisTemplate.opsForHyperLogLog().add(pfKey3, i);
    }
    for (int i = 10000; i < 20000; i++) {
        redisTemplate.opsForHyperLogLog().add(pfKey4, i);
    }
    // 合并三组数
    String unionKey = "test:hll:union";
    redisTemplate.opsForHyperLogLog().union(unionKey, pfKey2, pfKey3, pfKey4);

    // 统计合并后的基数
    long size = redisTemplate.opsForHyperLogLog().size(unionKey);
    System.out.println(size);
}
19891

二、BitMap-位图

1. BitMap数据结构特点

  • 使用位存储,信息状态只有 0 和 1
    • 可以将其看作Byte数组
    • 可以储存大量连续数据的布尔值

Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset)
在bitmap上可执行AND,OR,XOR,NOT以及其它位操作

bitmap的底层

在这里插入图片描述

  • 所以bitmaps是一串从左到右的二进制串

2. 应用场景

  • 签到统计、状态统计

统计用户信息,活跃,不活跃! 登录 、 未登录! 打卡,365打卡! 两个状态的,都可以使用
Bitmaps!

3. 常用API及Spring客户端测试

redis原生API
命令描述
setbit key offset value为指定key的offset位设置值
getbit key offset获取offset位的值
bitcount key [start end]统计字符串被设置为1的bit数,也可以指定统计范围按字节
bitop operration destkey key[key..]对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
BITPOS key bit [start] [end]返回字符串里面第一个被设置为1或者0的bit位。start和end只能按字节,不能按位
# 添加数据和获取数据
127.0.0.1:6379> setbit sign 0 1 # 设置sign的第0位为 1 
(integer) 0
127.0.0.1:6379> setbit sign 2 1 # 设置sign的第2位为 1  不设置默认 是0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> type sign	# 底层也是String类型
string

127.0.0.1:6379> getbit sign 2 # 获取第2位的数值
(integer) 1
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 4 # 未设置默认是0-及false
(integer) 0

# 统计数据中为1的个数-即为true的个数
127.0.0.1:6379> BITCOUNT sign # 统计sign中为1的位数
(integer) 4
Spring客户端操作API
opsForValue().setBit(bitKey, 1, true)- 设置状态
  • 默认每位的状态为false
opsForValue().getBit(bitKey, 0))- 查询状态
redisConnection.bitCount(bitKey.getBytes())- 统计状态为true的个数
/**
 * 测试对BitMaps的操作
 * 记录-查询和统计
 */
@Test
public void testBitMap() {
    String bitKey = "test:bit:01";
    // 记录数据状态-默认false
    redisTemplate.opsForValue().setBit(bitKey, 1, true);
    redisTemplate.opsForValue().setBit(bitKey, 4, true);
    redisTemplate.opsForValue().setBit(bitKey, 7, true);

    // 查询
    System.out.println(redisTemplate.opsForValue().getBit(bitKey, 0));
    System.out.println(redisTemplate.opsForValue().getBit(bitKey, 1));
    System.out.println(redisTemplate.opsForValue().getBit(bitKey, 2));

    // 统计
    Object execute = redisTemplate.execute(new RedisCallback() {
        @Override
        public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
            return redisConnection.bitCount(bitKey.getBytes());
        }
    });

    System.out.println(execute);
}
false
true
false
3
connection.bitOp()- 位运算
@Nullable
Long bitOp(RedisStringCommands.BitOperation var1, byte[] var2, byte[]... var3);
  • OR运算

    connection.bitOp(RedisStringCommands.BitOperation.OR,
            bitKeyOR.getBytes(), bitKey2.getBytes(), bitKey3.getBytes(), bitKey4.getBytes());
    
/**
 * OR运算
 * 统计3组数据的布尔值, 并对这3组数据做OR运算.
 */
@Test
public void testBitMapOperation() {
    String bitKey2 = "test:bm:02";
    redisTemplate.opsForValue().setBit(bitKey2, 0, true);
    redisTemplate.opsForValue().setBit(bitKey2, 1, true);
    redisTemplate.opsForValue().setBit(bitKey2, 2, true);

    String bitKey3 = "test:bm:03";
    redisTemplate.opsForValue().setBit(bitKey3, 2, true);
    redisTemplate.opsForValue().setBit(bitKey3, 3, true);
    redisTemplate.opsForValue().setBit(bitKey3, 4, true);

    String bitKey4 = "test:bm:04";
    redisTemplate.opsForValue().setBit(bitKey4, 4, true);
    redisTemplate.opsForValue().setBit(bitKey4, 5, true);
    redisTemplate.opsForValue().setBit(bitKey4, 6, true);

    // 合并处理
    String bitKeyOR = "test:bm:or";
    Object obj = redisTemplate.execute(new RedisCallback() {
        @Override
        public Object doInRedis(RedisConnection connection) throws DataAccessException {
            connection.bitOp(RedisStringCommands.BitOperation.OR,
                    bitKeyOR.getBytes(), bitKey2.getBytes(), bitKey3.getBytes(), bitKey4.getBytes());
            return connection.bitCount(bitKeyOR.getBytes());
        }
    });

    System.out.println(obj);    // 统计的个数

    // 合并后,每位的状态
    System.out.println(redisTemplate.opsForValue().getBit(bitKeyOR, 0));
    System.out.println(redisTemplate.opsForValue().getBit(bitKeyOR, 1));
    System.out.println(redisTemplate.opsForValue().getBit(bitKeyOR, 2));
    System.out.println(redisTemplate.opsForValue().getBit(bitKeyOR, 3));
    System.out.println(redisTemplate.opsForValue().getBit(bitKeyOR, 4));
    System.out.println(redisTemplate.opsForValue().getBit(bitKeyOR, 5));
    System.out.println(redisTemplate.opsForValue().getBit(bitKeyOR, 6));
}
7
true
true
true
true
true
true
true
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值