位图由一系列二进制位组成,每个位可以被设置为1或0,当我们在处理需要高效存储和操作大量二进制位数据的适合,位图是一个非常有用的工具。
位图操作命令有:
- SETBIT:设置位图中指定位置的位的值。可以将位设置为 0 或 1。
- GETBIT:获取位图中指定位置的位的值。
- BITCOUNT:计算位图中置为 1 的位的数量。
- BITOP:对多个位图执行逻辑运算(AND、OR、XOR、NOT)。
- BITFIELD:执行复杂的位字段操作,允许你在位图上进行位级别的读写操作。
其中,用的最多的是前三个操作,示例如下:
位图的应用十分广泛,包括但不限于以下几方面:
- 统计用户活跃度:可以使用位图追踪用户的登录活动,每个用户对应一个位图,每天的登录状态可以用一个二进制位表示,通过 BITOP 命令可以计算多个用户的交集,从而得到活跃用户的统计信息。
- 数据压缩:位图可以高效地存储大量的二进制数据,比如布隆过滤器(Bloom Filter)就是基于位图实现的一种数据结构,用于快速判断元素是否存在。
- 事件计数:可以使用位图记录每天不同时间段的事件发生情况,比如网站的访问量,每个时间段对应一个位图,每次事件发生时将对应的位设置为 1,通过 BITCOUNT 命令可以计算出每个时间段的事件数量。
- 权限管理:可以使用位图来管理用户的权限,每个用户对应一个位图,每个权限对应一个二进制位,通过 BITOP 命令可以进行权限的并集、交集等操作。
RedisTemplate操作位图
在之前的几篇文章中,我们总结了一个Redis工具类,但是那个工具类中,并没有和位图相关的操作,这里添加和位图操作相关的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // value: true为1, false为0 public boolean setBit(String key, int offset, boolean value) { return redisTemplate.opsForValue().setBit(key, offset, value); } public boolean getBit(String key, int offset) { return redisTemplate.opsForValue().getBit(key, offset); } /** * 统计对应值为1 的数量 * @param key * @return */ public long bitCount(String key) { if (StringUtils.isEmpty(key)) { return 0L; } return redisTemplate.execute((RedisCallback) con -> con.bitCount(key.getBytes())); } /** * 统计在字节范围内,对应值为1的数量 * @param key * @param start * @param end * @return */ public Long bitCount(String key, long start, long end) { return redisTemplate.execute((RedisCallback) con -> con.bitCount(key.getBytes(), start, end)); } |
添加测试类,用于测试位图操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package org.example; import org.example.util.RedisUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class RedisBitMapTest { @Autowired private RedisUtils redisUtils; @Test public void testBitMap() { redisUtils.setBit( "bit" , 0 , true ); redisUtils.setBit( "bit" , 1 , true ); redisUtils.setBit( "bit" , 3 , true ); redisUtils.setBit( "bit" , 7 , true ); System.out.println(redisUtils.bitCount( "bit" )); } } |
执行结果如下:
我们通过Redis可视化工具,查看bit的值,可以看出其二进制值与我们操作的一致
位图应用之签到
在很多时候,我们遇到用户签到的场景,用户进入应用时,获取用户当天的签到情况,如果没有签到,用户可以签到,一般这种功能,可以通过set数据结构或bitMap来实现,但bitMap和set相比,其占用的空间更小,因此我们选择使用bitMap来实现签到的功能。
SignService:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | package org.example.util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.BitFieldSubCommands; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.time.LocalDate; import java.util.List; @Service public class SignService { @Autowired private RedisUtils redisUtils; @Autowired private RedisTemplate redisTemplate; /** * 签到 * @param id */ public void sign(Integer id) { LocalDate now = LocalDate.now(); String key = buildCacheKey(id, now); int dayOfMonth = now.getDayOfMonth(); // 签到 redisUtils.setBit(key, dayOfMonth, true ); } /** * 判断是否签到 */ public boolean isSign(Integer id) { LocalDate now = LocalDate.now(); String key = buildCacheKey(id, now); int dayOfMonth = now.getDayOfMonth(); return redisUtils.getBit(key, dayOfMonth); } /** * 获取当月的签到次数 * @param id * @return */ public Long getSignCountOfThisMonth(Integer id) { LocalDate now = LocalDate.now(); String key = buildCacheKey(id, now); int dayOfMonth = now.getDayOfMonth(); List result = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create() .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt( 1 )); if (result == null || result.isEmpty()) { return 0L; } Long num = result.get( 0 ); if (num == null || num == 0 ) { return 0L; } String binaryStr = Long.toString(num, 2 ); long count = 0 ; for ( int i = 0 ; i result = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create() .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt( 1 )); if (result == null || result.isEmpty()) { return 0L; } Long num = result.get( 0 ); if (num == null || num == 0 ) { return 0L; } long count = 0 ; while ( true ) { if ((num & 1 ) == 0 ) { break ; } else { count ++; } num >>>= 1 ; } return count; } private String buildCacheKey(Integer id, LocalDate localDate) { int year = localDate.getYear(); int monthValue = localDate.getMonthValue(); String key = "sign:" + year + ":" + monthValue + ":" + id; return key; } } |
测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Autowired private SignService signService; @Test public void testSign() { // 签到 signService.sign( 1 ); // 判断是否签到 System.out.println( "是否签到:" + signService.isSign( 1 )); // 获取当月的签到次数 System.out.println( "当月的签到次数:" + signService.getSignCountOfThisMonth( 1 )); // 获取当月的连续签到次数 System.out.println( "当月连续签到次数:" + signService.getContinuousSignCountOfThisMonth( 1 )); } |
运行结果如下:
到此这篇关于Redis应用之签到的使用的文章就介绍到这了,更多相关Redis 签到内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!