Loading

布隆过滤器的简单实现

原理: 布隆过滤器是采用 BitMap 来实现的, 大致原理是: 将系统中所有存在的 key 经过 hash 运行后放入 bitmap, 在查询之前先通过 bitmap 过滤掉一定不存在的 key。

缺点是: 因为有 hash 冲突的问题, 所以通过布隆过滤器的数据也有可能是系统中不存在的 key, 但是没有通过布隆过滤器的 key 一定是系统中不存在的。

package com.xtyuns.common;

@FunctionalInterface
public interface HashFunction {
    long hash(String str);
}
package com.xtyuns.common;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;

@Component
public class RedisBloomFilter {
    @Autowired
    private StringRedisTemplate redisTemplate;
    static final int DEFAULT_SIZE = 1 << 14; // aka 16k
    static final int[] DEFAULT_SEEDS = {31, 73, 101, 127, 179, 223};

    /**
     * 返回给定数字的 2 的次幂形式。
     */
    static int alignSize(int size) {
        int n = -1 >>> Integer.numberOfLeadingZeros(size - 1);
        return (n < 0) ? 1 : (n == Integer.MAX_VALUE) ? Integer.MAX_VALUE : n + 1;
    }

    /**
     * 根据数据容量和散列种子数组来构造散列函数列表
     */
    static HashFunction[] createHahFunctions(int alignedSize, int[] seeds) {
        HashFunction[] functions = new HashFunction[seeds.length];
        for (int i = 0; i < seeds.length; i++) {
            final int finalI = i;
            functions[i] = str -> {
                int seed = seeds[finalI], h = 0;
                for (byte v : str.getBytes(StandardCharsets.UTF_8)) {
                    h = seed * h + (v & 0xff);
                }
                return h & alignedSize - 1;
            };
        }
        return functions;
    }

    /**
     * 散列函数列表
     */
    transient HashFunction[] hashFunctions;

    /**
     * 存放在 redis 中的数据容器的键名称
     */
    final String BLOOM_KEY_IN_REDIS = "bitmap:redis_bloom_filter";

    /**
     * 根据指定的容量和散列种子数组构造 RedisBloomFilter
     *
     * @param size 数据容器的规模
     * @param seeds 散列函数列表的种子
     * @throws IllegalArgumentException 如果数据规模为负数或散列种子数组为空时
     */
    public RedisBloomFilter(int size, int ... seeds) {
        if (size < 0)
            throw new IllegalArgumentException("无效的容器空间: " + size);
        if (null == seeds || seeds.length == 0)
            throw new IllegalArgumentException("散列种子数组不能为空");
        this.hashFunctions = createHahFunctions(alignSize(size), seeds);
    }

    /**
     * 根据指定的容量和默认散列种子数组构造 RedisBloomFilter
     * @param size 数据容器的大小
     * @throws IllegalArgumentException 如果数据容量为负数时
     *
     */
    public RedisBloomFilter(int size) {
        this(size, DEFAULT_SEEDS);
    }

    /**
     * 使用默认参数构造 RedisBloomFilter
     */
    public RedisBloomFilter() {
        this(DEFAULT_SIZE, DEFAULT_SEEDS);
    }

    /**
     * 检查 IOC 容器是否成功注入 redis 操作对象
     */
    @PostConstruct
    public void checkProperties() {
        if (null == this.redisTemplate) throw new RuntimeException("redis 初始化失败!");
    }

    /**
     * 判断给定的字符串数据是否可能已存在, 布隆过滤器可能存在误判
     */
    public boolean mightContains(String str) {
        for (HashFunction hashFunction : this.hashFunctions) {
            long hash = hashFunction.hash(str);
            Boolean bit = redisTemplate.opsForValue().getBit(BLOOM_KEY_IN_REDIS, hash);
            if (null == bit || !bit) return false;
        }
        return true;
    }

    /**
     * 向数据容器中添加给定的字符串数据
     */
    public void add(String str) {
        for (HashFunction hashFunction : this.hashFunctions) {
            long hash = hashFunction.hash(str);
            redisTemplate.opsForValue().setBit(BLOOM_KEY_IN_REDIS, hash, true);
        }
    }
}

posted @ 2021-11-22 14:43  xtyuns  阅读(138)  评论(0编辑  收藏  举报