前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >redis之bitmap

redis之bitmap

作者头像
九转成圣
发布2024-04-10 16:58:42
1170
发布2024-04-10 16:58:42
举报
文章被收录于专栏:csdncsdn

redis之bitmap

redis在线网址

https://try.redis.io/

二值统计:只有两个数的统计,要么0要么1

bitmap底层也是动态字符串(不需要初始化字符串,就可以往字符串里面存,如果不存在就创建,若果空间不足则扩容)

代码语言:javascript
复制
private void setBit() {
    // 初始化了一个9字节的字符串吗? 没有
    bitmapStatsService.setBit(USER_MONTH_SIGN, 3, true);
    bitmapStatsService.setBit(USER_MONTH_SIGN, 6, true);
    bitmapStatsService.setBit(USER_MONTH_SIGN, 9, true);
    redisTemplate.expire(USER_MONTH_SIGN, 1, TimeUnit.DAYS);
}

bitmap相关API

代码语言:javascript
复制
SETBIT key offset value
summary: Sets or clears the bit at offset in the string value stored at key
since: 2.2.0

GETBIT key offset
summary: Returns the bit value at offset in the string value stored at key
since: 2.2.0

BITCOUNT key [start end]
summary: Count set bits in a string
since: 2.6.0

BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
summary: Perform arbitrary bitfield integer operations on strings
since: 3.2.0

BITOP operation destkey key [key ...]
summary: Perform bitwise operations between strings
since: 2.6.0

BITPOS key bit [start] [end]
summary: Find first bit set or clear in a string
since: 2.8.7

RedisTemplate操作bitmap

代码语言:javascript
复制
@Component
public class BitmapService {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    // 设置指定位置的位值
    public void setBit(String key, long offset, boolean value) {
        redisTemplate.opsForValue().setBit(key, offset, value);
    }

    // 获取指定位置的位值
    public Boolean getBit(String key, long offset) {
        return redisTemplate.opsForValue().getBit(key, offset);
    }

    // 统计指定范围内的位值为 true 的个数
    public Long bitCount(String key) {
        return redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitCount(key.getBytes()));
    }

    public Long bitCount(String key, long start, long end) {
        return redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitCount(key.getBytes(), start, end));
    }

    // 首签时间
    public Long bitPos(String key, boolean bit) {
        return redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitPos(key.getBytes(), bit));
    }

    public Long bitPos(String key, boolean bit, Long start, Long end) {
        Range<Long> range = Range.closed(start, end);
        return redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitPos(key.getBytes(), bit, range));
    }

    // 连续签到/累计签到
    public Long bitOp(RedisStringCommands.BitOperation op, String destination, String... keys) {
        byte[][] keyBytes = new byte[keys.length][];
        for (int i = 0; i < keys.length; i++) {
            keyBytes[i] = keys[i].getBytes();
        }
        return redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitOp(op, destination.getBytes(), keyBytes));
    }
}

测试

代码语言:javascript
复制
@Slf4j
@RestController
public class HelloBitMapController {
    @Autowired
    private BitmapService bitmapStatsService;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private static final String USER_MONTH_SIGN = "sign:uid:month:1:202306";
    private static final String weekSignAndKey = "week:sign:and";
    private static final String weekSignOrKey = "week:sign:or";

    @RequestMapping("/helloBitmap")
    public void helloBitmap() {
        setBit();
        getBit();
        bitCount();
        bitPos();
    }

    // 首签 累计签到 连续签到
    private void setBit() {
        bitmapStatsService.setBit(USER_MONTH_SIGN, 3, true);
        bitmapStatsService.setBit(USER_MONTH_SIGN, 6, true);
        bitmapStatsService.setBit(USER_MONTH_SIGN, 9, true);
        redisTemplate.expire(USER_MONTH_SIGN, 1, TimeUnit.DAYS);
    }


    private void getBit() {
        // 可不初始化(string) 不一定非要从0开始,可以用1代表1号
        Boolean bit3 = bitmapStatsService.getBit(USER_MONTH_SIGN, 3);
        log.info("3号签到情况:{}", bit3);
        Boolean bit6 = bitmapStatsService.getBit(USER_MONTH_SIGN, 6);
        log.info("6号签到情况:{}", bit6);
        Boolean bit9 = bitmapStatsService.getBit(USER_MONTH_SIGN, 9);
        log.info("9号签到情况:{}", bit9);
    }

    private void bitCount() {
        Long count = bitmapStatsService.bitCount(USER_MONTH_SIGN);
        log.info("累计签到{}天", count != null ? count : 0L);
    }


    private void bitPos() {
        Long pos = bitmapStatsService.bitPos(USER_MONTH_SIGN, true);
        log.info("首签{}号", pos);
    }

    // 不用bitOp也能实现统计(bitCount,感觉还要简单一些,至于那个更合适有待进一步思考)
    @RequestMapping("/bitOp")
    public void bitOp() {
        // 1号用户7天全签到
        weekSign(1, true);
        // 2号用户周一没有签到
        weekSign(2, true);
        bitmapStatsService.setBit("week:sign:1", 2, false);
        // 3号用户只有周1签到
        bitmapStatsService.setBit("week:sign:1", 3, true);
        // 输出签到结果
        show();
        // 统计结果
        bitOpAnd();
        bitOpOr();
    }

    private void show() {
        int[] userIds = {1, 2, 3};
        List<StringBuilder> list = new ArrayList<>();
        for (String s : getWeekSignKey()) {
            StringBuilder sb = new StringBuilder();
            for (int userId : userIds) {
                Boolean bit = bitmapStatsService.getBit(s, userId);
                sb.append(bit ? "1\t" : "0\t");
            }
            list.add(sb);
        }
        for (StringBuilder sb : list) {
            System.out.println(sb);
        }
        System.out.println("----------------------");
        StringBuilder and = new StringBuilder();
        for (int userId : userIds) {
            Boolean bit = bitmapStatsService.getBit(weekSignAndKey, userId);
            and.append(bit ? "1\t" : "0\t");
        }
        System.out.println(and);
        System.out.println("----------------------");
        StringBuilder or = new StringBuilder();
        for (int userId : userIds) {
            Boolean bit = bitmapStatsService.getBit(weekSignOrKey, userId);
            or.append(bit ? "1\t" : "0\t");
        }
        System.out.println(or);
        for (int userId : userIds) {
            allSign(userId);
            anySign(userId);
        }
    }

    public String[] getWeekSignKey() {
        String[] keys = new String[7];
        // 按天记录 week:sign:1 周一签到情况,week:sign:2 周二签到情况...
        String userSignKey = "week:sign:${week}";
        for (int i = 1; i <= 7; i++) {
            String key = userSignKey.replace("${week}", String.valueOf(i));
            keys[i - 1] = key;
        }
        System.out.println(Arrays.toString(keys));
        return keys;
    }

    public void weekSign(int userId, boolean value) {
        String[] keys = getWeekSignKey();
        for (String key : keys) {
            System.out.println(key);
            bitmapStatsService.setBit(key, userId, value);
            redisTemplate.expire(key, 1, TimeUnit.DAYS);
        }
    }

    private void bitOpAnd() {
        // 周签统计结果
        bitmapStatsService.bitOp(RedisStringCommands.BitOperation.AND, weekSignAndKey, getWeekSignKey());
    }

    private void bitOpOr() {
        // 周签统计结果
        bitmapStatsService.bitOp(RedisStringCommands.BitOperation.OR, weekSignOrKey, getWeekSignKey());
    }

    private void allSign(int userId) {
        Boolean bit = bitmapStatsService.getBit(weekSignAndKey, userId);
        log.info("用户{}是否7天有签到过:{}", userId, bit);
        redisTemplate.expire(weekSignAndKey, 1, TimeUnit.DAYS);
    }

    private void anySign(int userId) {
        Boolean bit = bitmapStatsService.getBit(weekSignOrKey, userId);
        log.info("用户{}是否7天都签到过:{}", userId, bit);
        redisTemplate.expire(weekSignOrKey, 1, TimeUnit.DAYS);
    }
}

redis命令操作bitmap

海量用户登录状态的保存

保存5000万用户的登录状态(大概需要50000000/8/1024/1024≈6M)

登录

代码语言:javascript
复制
setbit login_status 10086 1

判断是否登录

代码语言:javascript
复制
getbit login_status 10086

退出

代码语言:javascript
复制
setbit login_status 10086 0

用户签到以及统计

签到
代码语言:javascript
复制
setbit uid:sign:10086:202306 5 1
统计月签次数
代码语言:javascript
复制
bitcount uid:sign:10086:202306
查看首签时间
代码语言:javascript
复制
bitpos uid:sign:10086:202306 1
连续7天登录用户统计
代码语言:javascript
复制
setbit uid:sign:1 10086 1
setbit uid:sign:2 10086 1
setbit uid:sign:3 10086 1
setbit uid:sign:4 10086 1
setbit uid:sign:5 10086 1
setbit uid:sign:6 10086 1
setbit uid:sign:7 10086 1
# BITOP operation destkey key [key ...]
# opration 可以是 and、OR、NOT、XOR
# 7天连续登录
bitop and uid:sign:r uid:sign:1 uid:sign:2 uid:sign:3...
# 7天内有登录过
bitop or uid:sign:r uid:sign:1 uid:sign:2 uid:sign:3...

优点

  1. 节省空间
  2. 效率高setbit和getbit的时间复杂度都是O(1),其他位运算效率也高

缺点

  1. 本质上位只有0和1的区别,所以一般只能用于状态记录
  2. 对于需要统计的key的数量不大,但key与key之前跨度较大的场景不适用 比如要存入(10,8887983,93452134)这三个数据,我们需要建立一个 99999999 长度的 BitMap ,但是实际上只存了3个数据,这时候就有很大的空间浪费,碰到这种问题的话,可以通过引入 Roaring BitMap 来解决。

bitmap启发

10亿手机号排序

从一大堆数里面判断给定的数存不存在(类似布隆过滤器)

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-04-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • redis之bitmap
  • redis在线网址
  • bitmap相关API
  • RedisTemplate操作bitmap
    • 测试
    • redis命令操作bitmap
      • 海量用户登录状态的保存
        • 用户签到以及统计
          • 签到
          • 统计月签次数
          • 查看首签时间
          • 连续7天登录用户统计
      • 优点
      • 缺点
      • bitmap启发
        • 10亿手机号排序
        • 从一大堆数里面判断给定的数存不存在(类似布隆过滤器)
        相关产品与服务
        云数据库 Redis
        腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档