redis的数据类型

string:

实现类似于java的ArrayList,当串大小小于1MB时候,采用加倍扩容,当大于1MB时,则每次扩容只会多扩容1MB的空间,最大长度为512MB

相关指令:set key value get key mset key1 value1 key2 value2...keyn valuen mget key1..keyn setnx key value等

list:

实现类似于java的LinkedList,是双指针结构,但并不是常规的linkedlist,而是一个快速链表的结构,当元素比较少时候,分配一块连续的内存空间,使用ziplist压缩列表,当数据量比较多的时候,改为快速链表,快速链表通过把原来的linkedlist分割成一段段用ziplist存储,并把ziplist连接起来,由于定位速度相对较慢,插入删除比较快,适合对端操作如异步队列、栈等

相关指令: lpush lpop rpush rpop llen lindex ltrim等

hash(字典):

类似于hashMap,采用链地址法解决hash冲突,hash的值只能为字符串,rehash采用渐进式策略,rehash同时,会保留新旧两个hash结构,当查询时,会查询两个hash结构,搬迁完成时,旧的hash结构会被删除

相关指令 hset hgetall hlen hget hmset等

set:

hashset 特殊的字典,value为NULL值,去重

相关指令: sadd smembers  sismember scard spop

zset(有序列表):

类似于TreeMap,具有排序功能,使用跳表,具有多层索引的数据结构实现,zset中最后一个value被移除后,结构会被删除并回收内存

相关指令:zadd zrange zrevrange zcard zscore zrangebyscore zrem zrange

bitmap(位图):

位图相当于一个byte数组,每个bit取值只能为0或者1,如果某个偏移位置超出现有内容的范围,会进行零扩充,简单的例子

HyperLogLog

这个数据结构提供不太精确的去重计数方案,标准误差为0.81%,但对于统计大量数据来说,对比其他数据结构十分节省空间,用于解决精确度不是很高的统计问题比如统计某个页面的uv,实现比较复杂详细介绍

相关指令:pfadd pfcount pfmerge

BloomFilter 布隆过滤器

布隆过滤器可以理解为不太精确的set,它通过将一个key的hash值分布到一个大的bit数组上面,判断一个key是否存在时只需判断该的hash对应的bit位是否都是1,如果全是1则表示存在,否则不存在。性能相对较高并节省空间,但对于原先不存在的元素可能在判断时候存在误判比如对应的bit位刚好被其他的key置为1了

redis官方在4.0后以插件https://github.com/RedisBloom/RedisBloom的形式加载到redis server上去,而java方面的使用也可以使用JReBloomhttps://github.com/redisbloom/JReBloom来对应操作代码,但这里采用操作bitmap来实现布隆过滤器,模仿(大部分copy)guava的bloomfilter来实现

操作位图的接口

package com.gdut.imis.orderfood.bloomfilter;

/**
 * @author: lele
 * @date: 2019/12/20 上午11:31
 */
public interface BitArray<T> {
    void setBitSize(long bitSize);

    boolean set(long index);

    boolean setBatch(long[] index);

    boolean get(long index);

    boolean getBatch(long[] index);

    long bitSize();
}

redis的位图操作实现类,这里使用redistemplate+pipline操作

package com.gdut.imis.orderfood.bloomfilter;


import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.List;

/**
 * @author: lele
 * @date: 2019/12/20 上午11:39
 * 操作redis的bitset
 */
public class RedisTemplateBitArray implements BitArray {


    private RedisTemplate redisTemplate;

    private String key;

    private long bitSize;

    /**
     * 2^32
     */
    public static final long MAX_REDIS_BIT_SIZE = 4_294_967_296L;

    public static final String KEY_PREFIX = "bloom_filter:";


    public RedisTemplateBitArray(RedisTemplate redisTemplate, String key) {
        this.redisTemplate = redisTemplate;
        this.key = KEY_PREFIX + key;
    }


    @Override
    public void setBitSize(long bitSize) {
        if (bitSize > MAX_REDIS_BIT_SIZE) {
            throw new IllegalArgumentException("Invalid redis bit size, must small than 2 to the 32");
        }
        this.bitSize = bitSize;
    }

    @Override
    public boolean set(long index) {
        boolean result;
        result = (boolean) redisTemplate.execute((RedisCallback) redisConnection -> redisConnection.setBit(key.getBytes(), index, true).booleanValue());
        return result;
    }

    @Override
    public boolean setBatch(long[] index) {
        redisTemplate.executePipelined((RedisCallback) redisConnection -> {
            redisConnection.openPipeline();
            for (long l : index) {
                redisConnection.setBit(key.getBytes(), l, true);
            }
            return null;
        });
        return Boolean.TRUE;
    }

    @Override
    public boolean get(long index) {
        boolean result;
        result = (boolean) redisTemplate.execute((RedisCallback) redisConnection -> redisConnection.getBit(key.getBytes(), index));
        return result;
    }

    @Override
    public boolean getBatch(long[] index) {
        return !redisTemplate.executePipelined((RedisCallback<List<Boolean>>) redisConnection -> {
            redisConnection.openPipeline();
            for (long l : index) {
                //如果其中一个没有,那就是没有
                redisConnection.getBit(key.getBytes(), l);
            }
            return null;
        }).contains(Boolean.FALSE);

    }


    @Override
    public long bitSize() {
        return this.bitSize;
    }
}

bloomfiter类

package com.gdut.imis.orderfood.bloomfilter;

import com.google.common.base.Preconditions;
import com.google.common.hash.Funnel;

import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author: lele
 * @date: 2019/12/20 上午11:35
 */
public class CustomBloomFilter<T> {
    interface Strategy {
        <T> boolean put(T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bits, boolean batch);

        <T> boolean mightContain(T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bits, boolean batch);

        /**
         * 批量添加使用
         *
         * @param consumer
         * @param value
         * @return
         */
        static boolean batchAdd(Consumer<Long> consumer, long[] value) {
            for (long l : value) {
                consumer.accept(l);
            }
            return Boolean.TRUE;
        }

        /**
         * 批量获取使用
         *
         * @param function
         * @param value
         * @return
         */
        static boolean batchGet(Function<Long, Boolean> function, long[] value) {
            for (long l : value) {
                if (!function.apply(l)) {
                    return Boolean.FALSE;
                }
            }
            return Boolean.TRUE;
        }
    }

    /**
     * 操作位图的接口
     */
    private final BitArray bits;

    /**
     * hash函数个数k
     */
    private final int numHashFunctions;
    /**
     * 将传入的实例转换基本类型 char,int,long,byte
     */
    private final Funnel<? super T> funnel;

    /**
     * hash策略
     */
    private final Strategy strategy;

    /**
     * 自定义属性,批量操作
     */
    private final Boolean batch;


    /**
     * expectedInsertions 期望插入元素的个数
     * fpp 错误率
     */
    public static <T> CustomBloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions, double fpp, Strategy strategy, BitArray bits, Boolean batch) {
        Preconditions.checkArgument(
                expectedInsertions >= 0, "Expected insertions (%s) must be >= 0", expectedInsertions);
        Preconditions.checkArgument(fpp > 0.0, "False positive probability (%s) must be > 0.0", fpp);
        Preconditions.checkArgument(fpp < 1.0, "False positive probability (%s) must be < 1.0", fpp);
        Preconditions.checkNotNull(strategy);

        if (expectedInsertions == 0) {
            expectedInsertions = 1;
        }

        long numBits = optimalNumOfBits(expectedInsertions, fpp);
        bits.setBitSize(numBits);
        int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
        return new CustomBloomFilter<>(bits, numHashFunctions, funnel, strategy, batch);
    }


    public static <T> CustomBloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions, double fpp, BitArray bitArray) {
        return create(funnel, expectedInsertions, fpp, CustomBloomFilterStrategies.MURMUR128_MITZ_64, bitArray, Boolean.TRUE);
    }

    public static <T> CustomBloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions, BitArray bitArray) {
        return create(funnel, expectedInsertions, 0.03, CustomBloomFilterStrategies.MURMUR128_MITZ_64, bitArray, Boolean.TRUE);
    }

    public static <T> CustomBloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions, BitArray bitArray, Boolean batch) {
        return create(funnel, expectedInsertions, 0.03, CustomBloomFilterStrategies.MURMUR128_MITZ_64, bitArray, batch);
    }

    private CustomBloomFilter(BitArray bits,
                              int numHashFunctions,
                              Funnel<? super T> funnel,
                              Strategy strategy,
                              Boolean batch) {
        this.bits = bits;
        this.numHashFunctions = numHashFunctions;
        this.strategy = strategy;
        this.funnel = funnel;
        this.batch = batch;
    }

    public boolean mightContain(T object) {
        return strategy.mightContain(object, funnel, numHashFunctions, bits, batch);
    }


    public void put(T object) {
        strategy.put(object, funnel, numHashFunctions, bits, batch);
    }

    static int optimalNumOfHashFunctions(long n, long m) {
        // (m / n) * log(2), but avoid truncation due to division!
        return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
    }

    static long optimalNumOfBits(long n, double p) {
        if (p == 0) {
            p = Double.MIN_VALUE;
        }
        return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
    }

}

哈希策略

package com.gdut.imis.orderfood.bloomfilter;

import com.baomidou.mybatisplus.extension.api.R;
import com.google.common.hash.Funnel;
import com.google.common.hash.Hashing;
import com.google.common.primitives.Longs;

import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author: lele
 * @date: 2019/12/20 上午11:39
 * 沿用guava的hash方法
 */
public enum CustomBloomFilterStrategies implements CustomBloomFilter.Strategy {


    MURMUR128_MITZ_64 {
        @Override
        public <T> boolean put(T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bitArray, boolean batch) {
            long bitSize = bitArray.bitSize();
            byte[] bytes = Hashing.murmur3_128().hashObject(object, funnel).asBytes();
            long[] indexs = getIndex(bytes, numHashFunctions, bitSize);
            return batch ? bitArray.setBatch(indexs) : CustomBloomFilter.Strategy.batchAdd(e -> bitArray.set(e), indexs);
        }

        @Override
        public <T> boolean mightContain(T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bitArray, boolean batch) {
            long bitSize = bitArray.bitSize();
            byte[] bytes = Hashing.murmur3_128().hashObject(object, funnel).asBytes();
            long[] indexs = getIndex(bytes, numHashFunctions, bitSize);
            return batch ? bitArray.getBatch(indexs) : CustomBloomFilter.Strategy.batchGet(e -> bitArray.get(e), indexs);
        }

        private long lowerEight(byte[] bytes) {
            return Longs.fromBytes(bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);
        }

        private long upperEight(byte[] bytes) {
            return Longs.fromBytes(bytes[15], bytes[14], bytes[13], bytes[12], bytes[11], bytes[10], bytes[9], bytes[8]);
        }


        private long[] getIndex(byte[] bytes, int numHashFunctions, long bitSize) {
            long hash1 = this.lowerEight(bytes);
            long hash2 = this.upperEight(bytes);
            long combinedHash = hash1;
            long[] batchIndex = new long[numHashFunctions];
            for (int i = 0; i < numHashFunctions; ++i) {
                batchIndex[i] = (combinedHash & 9223372036854775807L) % bitSize;
                combinedHash += hash2;
            }
            return batchIndex;
        }
    },
    MURMUR128_MITZ_32() {
        @Override
        public <T> boolean put(T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bitArray, boolean batch) {
            long bitSize = bitArray.bitSize();
            long hash64 = Hashing.murmur3_128().hashObject(object, funnel).asLong();
            long[] indexs = getIndex(hash64, numHashFunctions, bitSize);
            return batch ? bitArray.setBatch(indexs) : CustomBloomFilter.Strategy.batchAdd(e -> bitArray.set(e), indexs);

        }

        @Override
        public <T> boolean mightContain(T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bitArray, boolean batch) {
            long bitSize = bitArray.bitSize();
            long hash64 = Hashing.murmur3_128().hashObject(object, funnel).asLong();
            long[] indexs = getIndex(hash64, numHashFunctions, bitSize);
            return batch ? bitArray.getBatch(indexs) : CustomBloomFilter.Strategy.batchGet(e -> bitArray.get(e), indexs);

        }

        long[] getIndex(long hash64, int numHashFunctions, long bitSize) {
            int hash1 = (int) hash64;
            int hash2 = (int) (hash64 >> 32);
            long[] batchIndex = new long[numHashFunctions];
            //根据传进的hash函数个数来确定进行多少次hash计算
            for (int i = 1; i <= numHashFunctions; i++) {
                int combinedHash = hash1 + (i * hash2);
                if (combinedHash < 0) {
                    combinedHash = ~combinedHash;
                }
                batchIndex[i - 1] = combinedHash % bitSize;
            }
            return batchIndex;
        }
    }


}
@Test
   public void testBloom(){
       RedisTemplateBitArray array=new RedisTemplateBitArray(redisTemplate,"test");
       CustomBloomFilter<String> bloomFilter = CustomBloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), 1000,  array);
       String testElement1 = "hi";
       String testElement2 = "hello";
       String testElement3 = "789";
       bloomFilter.put(testElement1);
       bloomFilter.put(testElement2);
       System.out.println(bloomFilter.mightContain(testElement1));
       System.out.println(bloomFilter.mightContain(testElement2));
       System.out.println(bloomFilter.mightContain(testElement3));
   }

Geo 地理位置

实现其实是一个zset结构,可以用来获取两点之间的距离,获取一定范围内的地理位置比如附近门店,附近的人等等,简单的理解的话,他是使用geohash算法,它将二位的经纬度数据映射到一维的点,变成一个整数,score就是使用geohash进行处理后的值,通过排序可以获得对应坐标的附近元素

geoadd添加key对应member的经纬度

可以用zrem key member删除对应的成员

获取两点的距离

获取经纬度或者hash值,hash值可以去http://geohash.org去查

根据现有成员获取对应的自己及其他成员位置信息

根据给定的经纬度获取

基于redisTemplate部分代码实现:

GeoOperations<String, String> stringStringGeoOperations = redisTemplate.opsForGeo();
        Point point =new Point(113.3261100000,23.1438700000);
        Point point2 =new Point(113.3620000000,23.1959400000);
        Point point3 =new Point(113.3199356300,23.1450263200);
        stringStringGeoOperations.add("location_store_list",point,"海航");
        stringStringGeoOperations.add("location_store_list",point2,"龙洞");
        stringStringGeoOperations.add("location_store_list",point3,"中信的麦当劳");
        double value = stringStringGeoOperations.distance("location_store_list", "海航", "龙洞",RedisGeoCommands.DistanceUnit.KILOMETERS).getValue();
        System.out.println(value);
        Point here = new Point(113.3246700000,23.1421130000);
        Distance distance=new Distance(100, Metrics.KILOMETERS);
        Circle circle=new Circle(here,distance);
        RedisGeoCommands.GeoRadiusCommandArgs args=RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                .includeDistance().includeCoordinates().sortAscending().limit(3);
        GeoResults<RedisGeoCommands.GeoLocation<String>> store_list = stringStringGeoOperations.radius("location_store_list", circle, args);
        for (GeoResult<RedisGeoCommands.GeoLocation<String>> geoLocationGeoResult : store_list) {
            double resDistance = geoLocationGeoResult.getDistance().getValue();
            String name = geoLocationGeoResult.getContent().getName();
            Point point1 = geoLocationGeoResult.getContent().getPoint();
            System.out.printf("name:%s,distance:%s,x:%s,y:%s%n",name,resDistance,point1.getX(),point1.getY());
        }

<think>我们已经在之前的对话中讨论了Jedis的getbit方法,现在用户转向询问Redis位图(Bitmap)本身的数据类型介绍。根据引用[2]的描述,位图在Redis中是通过字符串类型(string)实现的特殊数据结构。我们需要清晰地解释这一概念。 关键点: 1. 位图不是独立的数据类型,而是基于字符串类型的二进制操作抽象。 2. 每个字节的8个比特可以被单独访问和操作。 3. 最大偏移量支持512MB(2^32位),但实际使用中要注意内存分配。 回答结构: 一、基本概念 二、底层实现原理 三、核心操作命令 四、典型应用场景 五、注意事项 另外,根据用户之前的上下文(他使用过JedisClient.getbit()),我们可以适当关联Jedis操作。 注意:引用[1]提到位运算的高效性,引用[2]给出了位图的例子,我们可以在适当位置标注引用。 同时,我们需要生成相关问题。 </think>### Redis位图(Bitmap)数据类型详解 #### 一、基本概念 位图(Bitmap)**不是独立的Redis数据类型**,而是基于字符串(String)类型实现的二进制操作接口。它允许开发者将字符串视为连续的二进制位序列(bit array),每个位只能存储0或1值[^2]。 **核心特性**: 1. **内存高效**:每个用户仅需1bit存储空间 - 示例:记录10亿用户登录状态仅需约120MB内存($10^9$ bits ≈ 119.2MB) 2. **偏移量寻址**:通过数字偏移量(offset)访问位 3. **原子操作**:所有位操作保证原子性 #### 二、底层实现原理 ```mermaid graph LR A[位图操作] --> B[Redis字符串] B --> C[二进制数据存储] C --> D[字节数组] D --> E[每个字节=8位] ``` - **二进制映射**:每个字节($1$ Byte)存储8个位(bits) - **内存分配**:首次设置偏移量$n$时分配$\lceil(n+1)/8\rceil$字节 - 示例:设置`offset=10` → 分配2字节($16$ bits) - **自动填充**:未显式设置的位默认值为0 #### 三、核心操作命令 | 命令 | 参数 | 作用 | 时间复杂度 | |-----------|---------------------|-------------------------------|------------| | `SETBIT` | key, offset, value | 设置指定位值 | $O(1)$ | | `GETBIT` | key, offset | 获取指定位值 | $O(1)$ | | `BITCOUNT`| key [start end] | 统计值为1的位数 | $O(N)$ | | `BITOP` | AND/OR/XOR destkey srckey... | 位运算(与/或/异或) | $O(N)$ | | `BITPOS` | key bit [start] [end] | 查找首个指定值位 | $O(N)$ | #### 四、典型应用场景 1. **用户行为追踪** ```bash # 用户1001第7天签到(偏移量从0开始) > SETBIT user:1001:signins 6 1 # 查询签到状态 > GETBIT user:1001:signins 6 # 返回1 ``` 2. **实时统计系统** ```bash # 统计7月1日活跃用户数 > BITCOUNT active:2023-07-01 # 计算7月累计活跃用户(位图合并) > BITOP OR active:2023-07-total active:2023-07-01 active:2023-07-02 ... ``` 3. **权限控制系统** ```python # 权限位映射(Python示例) READ_PERM = 1 << 0 # 偏移量0 (00000001) WRITE_PERM = 1 << 1 # 偏移量1 (00000010) ADMIN_PERM = 1 << 2 # 偏移量2 (00000100) # 检查用户权限(Jedis实现) boolean canWrite = jedis.getbit("user:perms", WRITE_PERM); ``` #### 五、注意事项 1. **内存碎片风险** - 设置稀疏大偏移量(如$offset=10^9$)会强制分配125MB内存 - **解决方案**:预分配紧凑偏移范围(如用户ID取模) 2. **性能考量** - `BITCOUNT`/`BITOP`操作时间复杂度为$O(N)$,大数据集需谨慎 - **优化方案**:分割位图(如按时间分片)[^1] 3. **数据类型约束** - 对非字符串键执行位操作会返回`WRONGTYPE`错误 - **防护措施**: ```java if(!"string".equals(jedis.type(key))) { // 错误处理逻辑 } ``` > 💡 **关键认知**:位图本质是字符串的二进制解释接口,其操作不改变Redis底层字符串数据类型,但提供了特殊的二进制语义访问能力[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值