1.Bitmap 位图
1.1 Redis从2.2.0版本开始新增了setbit,getbit,bitcount等几个bitmap相关命令。虽然是新命令,但是并没有新增新的数据类型,因为setbit等命令只不过是在set上的扩展,因为redis存储String类型的数据是以二进制的形式存储,只不过bitmap是运用了二进制,不用String类型。
1.2 命令
命令 | 说明 |
---|---|
setbit key offset value | 设置指定键的二进制位图指定offset偏移量为value[0/1] |
getbit key offset | 获取指定键的二进制位图指定offset偏移量上的value |
bitcount key [start] [end] | 获取指定键的二进制位图所有的value为1有多少位 |
bitop [operation] destkey key [key …] | 对指定键做运算并把结果放入destkey目标键中,operation操作有 【AND 与运算 , OR 或运算 , XOR 异或运算 , NOT 非运算】 |
bitfield key [operation] [u/i]offset value | get是取出key对应的位图,指定value索引位开始,取offset位偏移量的二进制; |
1.3 命令demo演示
- setbit key offset value
设置指定key的offset位上的bit值为value,value只能是1或0,返回在offset处原来的bit值setbit lzj 0 1 #返回值 0
- getbit key offset
获取offset处的bit值getbit lzj 0 #返回值 1
- bitcount key [start] [end]
获取开始索引 到 结束索引 之间bit值为1 的数量bitcount lzj 0 -1 #返回值 1
- bitop [operation] destkey key [key …]
把指定的多个键的二进制进行计算操作,并把计算后的二进制放入指定键中,返回结果放入键中字符串长度,最多只能处理64个连续的位。setbit lzj1 0 1 bitop and result lzj lzj1 #返回值1
- bitfield key [operation] [u/i]offset value
get是取出key对应的位图,指定value索引位开始,取offset位偏移量的二进制;bitfield result get u8 0 #返回128 bitfield result get i8 0 #返回-128 #其他 还没有弄懂
1.4 下面可以用java代码通过redistemplate去获取一个字符串的二进制位图
@SpringBootTest(classes = RedissiondemoApplication.class)
class ShopServiceImplTest {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Test
void consumer() {
redisTemplate.opsForValue().set("lzj","jj");
String res = redisTemplate.opsForValue().get("lzj");
//输出一个String类型的bitmap位图
System.out.println(toBinary(res.getBytes()));
//输出十进制数组
System.out.println(Arrays.toString(res.getBytes()));
}
public static String toBinary(byte[] bytes){
//把字符串转成字符数组
StringBuilder result= new StringBuilder();
for(int i=0;i<bytes.length;i++){
//byte是8位的,而int是32位,直接转换会在前面补24个1,所以 & 0xFF 消除掉
StringBuilder binary= new StringBuilder(Integer.toBinaryString(bytes[i] & 0xFF));
int len=8-binary.length();
if(len>0){
//不足8位补够
for(int j=0;j<len;j++){
binary.insert(0,"0");
}
}
result.append(binary).append(" ");
}
return result.toString();
}
}
可以看到从右到左是从低位到高位,二进制(8bit)转化为 十进制数组。
1.5 Bitmap怎么使用,根据二进制形式 是0 1 组成,所以我们可以用来做为一个用户的签到,用户签到就是1,没有签到就是 0(因为限制大小时512M就是2^32-1位),下面我将实现一个简单的demo。
1.6
@SpringBootTest(classes = RedissiondemoApplication.class)
public class Demo1 {
@Autowired
private RedisTemplate<String,String> redisTemplate;
/**
* 用户签到
*
* @param uid 用户ID
* @param date 日期
* @return 之前的签到状态
*/
public boolean doSign(int uid, LocalDate date) {
int offset = date.getDayOfMonth() - 1;
return redisTemplate.opsForValue().setBit(buildSignKey(uid, date), offset, true);
}
/**
* 检查用户是否签到
*
* @param uid 用户ID
* @param date 日期
* @return 当前的签到状态
*/
public boolean checkSign(int uid, LocalDate date) {
int offset = date.getDayOfMonth() - 1;
return redisTemplate.opsForValue().getBit(buildSignKey(uid, date), offset);
}
/**
* 获取用户签到次数
*
* @param uid 用户ID
* @param date 日期
* @return 当前的签到次数
*/
public long getSignCount(int uid, LocalDate date) {
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(buildSignKey(uid, date).getBytes()));
}
public static String toBinary(byte[] bytes){
//把字符串转成字符数组
StringBuilder result= new StringBuilder();
for(int i=0;i<bytes.length;i++){
//byte是8位的,而int是32位,直接转换会在前面补24个1,所以 & 0xFF 消除掉
StringBuilder binary= new StringBuilder(Integer.toBinaryString(bytes[i] & 0xFF));
int len=8-binary.length();
if(len>0){
//不足8位补够
for(int j=0;j<len;j++){
binary.insert(0,"0");
}
}
result.append(binary).append(" ");
}
return result.toString();
}
/**
* 获取当月连续签到次数
*
* @param uid 用户ID
* @param date 日期
* @return 当月连续签到次数
*/
public long getContinuousSignCount(int uid, LocalDate date) {
int signCount = 0;
System.out.println(date.getDayOfMonth());
//命令: bitfield key get [u/i]offset value 此命令就是get取出key对应的位图,指定value索引位开始,取offset位偏移量的二进制
BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(date.getDayOfMonth())).valueAt(0);
List<Long> list = redisTemplate.opsForValue().bitField(buildSignKey(uid, date),bitFieldSubCommands);
if (list != null && list.size() > 0) {
// 取低位连续不为0的个数即为连续签到次数,需考虑当天尚未签到的情况
long v = list.get(0) == null ? 0 : list.get(0);
for (int i = 0; i < date.getDayOfMonth(); i++) {
//因为取的是无符号的二进制,所以右移一位,左移一位,依然相等只有是最右边是0[没签到]的时候,
if (v >> 1 << 1 == v) {
// 只有除了第一天以外没签到,才算断签。
if (i > 0) break;
} else {
// 签到了 签到数加1
signCount += 1;
}
//右移一位并赋值,相当于把最左边一位去除
v >>= 1;
}
}
return signCount;
}
/**
* 获取当月首次签到日期
*
* @param uid 用户ID
* @param date 日期
* @return 首次签到日期
*/
public LocalDate getFirstSignDate(int uid, LocalDate date) {
Long pos = redisTemplate.execute((RedisCallback<Long>) con -> con.bitPos(buildSignKey(uid, date).getBytes(), true));
return pos < 0 ? null : date.withDayOfMonth((int) (pos + 1));
}
/**
* 获取当月连续签到次数
*
* @param uid 用户ID
* @param date 日期
* @return 当月连续签到次数
*/
public long getContinuousSignCount(int uid, LocalDate date) {
int signCount = 0;
System.out.println(date.getDayOfMonth());
//命令: bitfield key get [u/i]offset value
BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(date.getDayOfMonth())).valueAt(0);
List<Long> list = redisTemplate.opsForValue().bitField(buildSignKey(uid, date),bitFieldSubCommands);
if (list != null && list.size() > 0) {
// 取低位连续不为0的个数即为连续签到次数,需考虑当天尚未签到的情况
long v = list.get(0) == null ? 0 : list.get(0);
for (int i = 0; i < date.getDayOfMonth(); i++) {
//因为取的是无符号的二进制,所以右移一位,左移一位,依然相等只有是最右边是0[没签到]的时候,
if (v >> 1 << 1 == v) {
// 只有除了第一天以外没签到,才算断签。
if (i > 0) break;
} else {
// 签到了 签到数加1
signCount += 1;
}
//右移一位并赋值,相当于把最左边一位去除
v >>= 1;
}
}
return signCount;
}
private static String formatDate(LocalDate date) {
return formatDate(date, "yyyyMM");
}
private static String formatDate(LocalDate date, String pattern) {
return date.format(DateTimeFormatter.ofPattern(pattern));
}
private static String buildSignKey(int uid, LocalDate date) {
return String.format("u:sign:%d:%s", uid, formatDate(date));
}
private Integer ID = 2017131132;
/**
* 当天签到
*/
@Test
void sign() {
LocalDate today = LocalDate.now().minusDays(1);
String todayDate = formatDate(today,"yyyyMMdd");
System.out.println(doSign(ID, today)==true?"已签到【" + todayDate + "】":"签到成功【" + todayDate + "】");
}
/**
* 检查当天是否签到
*/
@Test
void checkSign(){
LocalDate today = LocalDate.now().minusDays(1);
String todayDate = formatDate(today,"yyyyMMdd");
System.out.println(checkSign(ID,today)==true?"【" + todayDate + "】已签到":"【" + todayDate + "】未签到");
}
/**
* 获取今月 签到次数
*/
@Test
void getSignCount(){
LocalDate today = LocalDate.now();
long signCount = getSignCount(ID, today);
System.out.println(today.getMonth().getValue() + "月份签到了" + signCount + "次");
}
/**
* 获取连续签到次数
* 注意: 如果当天没有签到是不算做断签,因为当天可以签到,如果前天没有签到就是断签了,不属于连续签到计算范围
*/
@Test
void getContinuousSignCount(){
LocalDate today = LocalDate.now();
long continuousSignCount = getContinuousSignCount(ID, today);
System.out.println("连续签到" + continuousSignCount + "天");
}
/**
* 获取第一天签到
*/
@Test
void getFirstSignDate(){
LocalDate today = LocalDate.now();
LocalDate firstSignDate = getFirstSignDate(ID, today);
System.out.println("首次签到日期是" + formatDate(firstSignDate,"yyyyMMdd"));
}
/**
* 获取用户的签到信息
*/
@Test
void getSignInfo(){
LocalDate today = LocalDate.now();
getSignInfo(ID, today).forEach((k,v) -> {
System.out.println(k + " : " + (v ?"√":"×"));
});
}
}
2.GEO
redis 3.2版本以上 才能使用的API
API | 作用 |
---|---|
GEOADD key longitude latitude member | 用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中 |
GEOPOS key member [member …] | 用于从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。 |
GEODIST key member1 member2 [m/km/ft/mi] | 用于返回两个给定位置之间的距离。 |
GEORADIUS key longitude latitude radius [m/km/ft/mi] | 获取以指定坐标内半径为raduis的圆内的元素个数 |
GEORADIUSBYMEMBER key member radius [m/km/ft/mi] | 获取以指定键的成员的内半径为raduis的圆内的元素个数 |
GEOHASH key member [member …] | geohash 来保存地理位置的坐标。 |
- GEOADD key longitude latitude member
用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中geoadd lzj 113.762141 22.978418 "广东科技学院" geoadd lzj 113.769143 22.933629 "水濂山森林公园"
- GEOPOS key member [member …]
用于从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。geopos lzj "广东科技学院" geopos lzj "水濂山森林公园"
- GEODIST key member1 member2 [m/km/ft/mi]
用于返回两个给定位置之间的距离。
–geodist lzj "广东科技学院" "水濂山森林公园" km
- GEORADIUS key longitude latitude radius [m/km/ft/mi]
和GEODIST作用一样,但是 georadiusbymember 的中心点是由给定的位置元素决定的, 而不是使用经度和纬度来决定中心点。
– WITHDIST : 返回距离
– WITHCOORD : 返回经纬度
– WITHHASH : 返回geohash值
– [ASC/DESC] : 规定距离排序顺序
– COUNT : 返回的指定记录数georadius lzj 113 22 5 km
- GEORADIUSBYMEMBER key member radius [m/km/ft/mi]
获取以指定键的成员的内半径为raduis的圆内的元素个数
– WITHDIST : 返回距离
– WITHCOORD : 返回经纬度
– WITHHASH : 返回geohash值
– [ASC/DESC] : 规定距离排序顺序
– COUNT : 返回的指定记录数georadiusbymember lzj "广东科技学院" 7 km
- GEOHASH key member [member …]
geohash 来保存地理位置的坐标geohash lzj "广东科技学院"
一键查询淘宝/拼多多内部优惠券,每日大额外卖红包,购物省钱的宝藏工具