Redis 数据结构(三) HyperLogLog、位图、地理坐标
HyperLogLog 超日志,是一个专门计算集合的基数的概率算法。可以计算出集合的近似基数,标准误差为0.81%。它进行计算的内存是固定的(12kb。2的64次方)。
bitmap 位图,是由多个二进制位组成的数组,每个二进制位都有与之对应的偏移量,用户通过这些偏移量可以对位图中的一个或多个二进制位进行操作。它是在字符串的基础上实现的。
geo 地理坐标,用户可以将经纬度格式的地理坐标存储到Redis,并对这些坐标执行距离计算、范围操作等操作。其是在有序集合基础上实现的。
1 超日志 HyperLogLog
HyperLogLog 能够在占用极小内存的情况下估算一个集合中不重复元素的数量。所占用的内存是固定的(12kb),不会随着元素数量的变化而改变。
其主要用于需要统计大量唯一值数量但又对内存占用敏感的场景,如:统计网址一段时间的独立访客数量、估算广告的独立点击用户数。
添加 | PFADD (可用于唯一判断)。 |
统计 | PFCOUNT,其会将集合使用PFMERGE合并并存储到临时集合,操作完成后会将这个临时集合删除。 |
合并 | PFMERGE。 |
表 HyperLogLog 相关操作命令
1.1 示例
某网址每日访客数量在10w以上,需求是,统计每日不同ip的访问数量。及判断某ip是否已访问过网站。
public class IpUniqueCounter {
private final static Jedis jedis = RedisPool.getRedis();
private final static String COUNTER_KEY = "ip_unique_counter_hyperLogLog";
public static void addIp(String ... ips) {
jedis.pfadd(COUNTER_KEY,ips);
}
public static long count() {
return jedis.pfcount(COUNTER_KEY);
}
public static boolean has(String ip) {
return jedis.pfadd(COUNTER_KEY,ip) != 1;
}
public static void main(String[] args) {
addIp("127.0.0.1","0.0.0.1","192.168.0.1");
System.out.println(count());
System.out.println(has("127.0.0.1"));
System.out.println(has("127.0.0.2"));
System.out.println(has("127.0.0.2"));
}
}
2 位图 Bitmap
位图基于字符串数据类型构建,允许用户将字符串视为一系列的二进制位(bit),每个位可以是0或1.这种数据结构非常适合存储和操作大量布尔值状态的数据,具有高效的空间利用率和快速的位操作能力。
应用场景有:记录用户的签到情况、网站页面的访问次数,用户状态管理等。
设置与获取 | GETBIT、SETBIT |
统计 | BITCOUNT,统计1的数量 |
查找 | BITPOS 查找 0 或1首次出现的位置,可以指定范围。 |
位运算 | BITOP。可以执行AND(逻辑并)、OR(逻辑或)、XOR(逻辑异或)、NOT(逻辑非)中的任意运算。 |
整数运算 | BITFIELD,其子命令有SET、GET、INCRBY、OVERFLOW(用于处理溢出)。 |
表 位图相关操作命令
2.1 BITFIELD
用于处理整数相关运算。
命令格式:BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP | SAT | FAIL]
type: 指定数值的类型,需要以i或u位前缀,后跟位长度。例如u8, 表示无符号8位整数。i64,有符号64位整数。
value: 用于指定被设置的整数值,这个值的类型应该和type参数指定的类型一致。如果长度超过了type参数指定的类型,那么会将高位多出的长度阶段。例如 type指定位u4。 value位 17(1 0001),那么存储的值为0001。
offset: 设置起始偏移量,从0开始。可以是负数。 例如 5,表示从第6个二进制为开始。前面加上#号表示索引,表示第几个type指定的类型。 例如 type指定为 u4,那么#2 表示第3个4位无符号整数。
2.1.1 OVERFLOW
当使用SET 或 INCRBY子命令发生溢出时(即超出type指定的范围),可以在这些命令前面使用OVERFLOW 来指定溢出控制行为。
WRAP | 回绕,默认值,向上溢出的整数值将从类型的最小值开始重新计算,向下溢出的整数值从类型的最大值重新计算。 |
SAT | 饱和运算,向上溢出的整数值将被设置为类型最大值,向下溢出的整数则会被设置为类型的最小值。 |
FAIL | 检测到计算会引发溢出时拒绝执行计算,并返回空值。 |
表 OVERFLOW子命令的可选参数处理溢出
2.2 示例
用户行为记录器:根据用户id记录用户是否执行某个操作,即在位图上,id 数值位上设置为1,表示该用户进行过操作。
public class ActionRecorder {
private final static Jedis jedis = RedisPool.getRedis();
private final static String ACTION_KEY = "action_recorder_bitmap";
private final static String PERSON_ID_KEY = "action_recorder_person_id_string";
public static long getPersonId(long num) {
return jedis.incrBy(PERSON_ID_KEY,num);
}
public static void performBy(long userId) {
jedis.setbit(ACTION_KEY,userId,true);
}
public static boolean isPerformedBy(long userId) {
return jedis.getbit(ACTION_KEY,userId);
}
public static long countPerformed() {
return jedis.bitcount(ACTION_KEY);
}
public static void main(String[] args) {
performBy(getPersonId(1));
performBy(getPersonId(5));
performBy(getPersonId(5));
System.out.println(isPerformedBy(1));
System.out.println(isPerformedBy(0));
System.out.println(isPerformedBy(6));
System.out.println(isPerformedBy(11));
System.out.println(isPerformedBy(12));
System.out.println(countPerformed());
}
}
布尔矩阵:由布尔值组成的矩阵,通常用于表示散列结构。
public class ZeroOneMatrix {
private final static Jedis jedis = RedisPool.getRedis();
private final static String MATRIX_KEY = "matrix_bitmap2";
private final static long ROW_COUNT = 5;
private final static long COL_COUNT = 6;
private static long calculatePos(long row,long col) {
if (row >= ROW_COUNT) throw new RuntimeException("行数超出界限");
if (col >= COL_COUNT) throw new RuntimeException("列数超出界限");
return row * COL_COUNT + col;
}
public static void setVal(long row,long col,boolean val) {
jedis.setbit(MATRIX_KEY,calculatePos(row,col),val);
}
public static boolean getVal(long row,long col) {
return jedis.getbit(MATRIX_KEY,calculatePos(row, col));
}
public static void show() {
for (int i = 0; i < ROW_COUNT; i++) {
StringBuilder sb = new StringBuilder();
sb.append("row").append(i).append(":");
for (int j = 0; j < COL_COUNT; j++) {
sb.append(getVal(i,j)).append(" ");
}
System.out.println(sb);
}
}
public static void main(String[] args) {
setVal(0,4,true);
setVal(1,5, true);
setVal(4,0,true);
show();
}
}
3 地理坐标 GEO
本质是有序集合。每个元素包含三个属性:位置名称、经度、纬度。 经度和纬度会转换为一个hash值,这个相当于有序集合中的分值。
存储与获取 | GEOADD、GEOPOS |
距离 | GEODIST |
查找 | GEORADIUS、GEORADIUSBYMEMBER(根据位置名称及半径范围查找) |
hash值 | GEOHASH |
表 GEO 相关命令
3.1 示例
用户地理位置:用于存储用户位置(经纬度)信息,能计算用户直接的距离,用户周围的其他用户等。
public class GeoLocation {
private final static Jedis jedis = RedisPool.getRedis();
private final static String LOCATION_KEY = "location_geo";
public static void setLocation(String city,double longitude,double latitude) {
jedis.geoadd(LOCATION_KEY,longitude,latitude,city);
}
public static void showLocation(String city) {
System.out.println(jedis.geopos(LOCATION_KEY,city));
}
public static void showDist(String city1,String city2) {
System.out.println(jedis.geodist(LOCATION_KEY,city1,city2, GeoUnit.KM));
}
public static void nearby(String city,double radius) {
System.out.println(jedis.georadiusByMember(LOCATION_KEY,city,radius,GeoUnit.KM));
}
public static void main(String[] args) {
setLocation("北京",116.28,39.54);
setLocation("上海",121.29,31.14);
setLocation("广州",113.17,23.06);
setLocation("深圳",114.03,22.34);
showLocation("上海");
showDist("北京","深圳");
showDist("广州","深圳");
showDist("深圳","深圳");
nearby("深圳",200);
nearby("深圳",1300);
}
}
原文地址:https://blog.csdn.net/qq_25308331/article/details/144380562
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!