Redis 数据结构(三) HyperLogLog、位图、地理坐标

HyperLogLog 超日志,是一个专门计算集合的基数的概率算法。可以计算出集合的近似基数,标准误差为0.81%。它进行计算的内存是固定的(12kb。2^14 * 5  / 8  = 10240 bytes ≈ 10 KB + 额外开销2kb)。

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);
    }
}

Redis是一款内存数据库,支持多种基本数据结构,这些数据结构不仅易于理解和操作,还极大地提高了存储和检索数据的效率。以下是Redis的一些主要数据结构: 1. **String(字符串)**: 存储单个字符串值。键值对形式,键是唯一的,支持原子操作如INCR和DECR。 2. **List(链表)**: 列表是一种有序序列,可以在两端进行插入和删除操作。每个节点包含一个哈希表,可以存储额外信息。 3. **Set(集合)**: 集合是一组互斥的元素,不允许有重复。常用的集合操作包括SADD、SREM等。 4. **Sorted Set(有序集合)**: 和集合类似,但是每个元素都有一个分数,可以根据分数排序。常用操作有ZADD和ZRANK。 5. **Hash(哈希表)**: 存储的是键值对映射,类似于对象,每个字段关联一个字符串值,键是唯一的。 6. **Bitmaps(位图)**: 用于表示二进制数据,非常适合存储大量的布尔值。 7. **HyperLogLog(概率型基数估算)**: 用于快速估计数据集中唯一元素的数量,适合大量数据的去重。 8. **GeoLocation(地理位置)**: 存储经纬度坐标及相关的附加信息,常用于做地理范围查询。 9. **Server Side Scripts(服务器端脚本)**: 运行在Redis服务器上的lua脚本,允许复杂的业务逻辑处理。 这些数据结构使得Redis能够适应各种数据模型和应用需求,是其高效性能的关键之一。通过选择合适的数据结构,可以大大提升数据处理的灵活性和效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值