用Java实现布隆过滤器

java 同时被 2 个专栏收录
38 篇文章 2 订阅
7 篇文章 0 订阅

1.什么是布隆过滤器?

  • 布隆过滤器(Bloom Filter)是一个叫做 Bloom 的老哥于1970年提出的。
  • 实际上可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。
  • 它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

在这里插入图片描述

2.布隆过滤器的原理介绍

  • 图解:

在这里插入图片描述

  • 如图所示,布隆过滤器添加元素时,该元素首先由多个哈希函数生成不同的哈希值,然后在对应的位数组的下表的元素设置为 1(当位数组初始化时 ,所有位置均为0)。当第二次存储相同字符串时,因为先前的对应位置已设置为1,所以很容易知道此值已经存在。

  • 如果我们需要判断某个元素是否在布隆过滤器中时,只需要对给定字符串再次进行相同的哈希计算,得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。

  • 不同的字符串可能哈希出来的位置相同,这种情况我们可以适当增加位数组大小或者调整我们的哈希函数。

  • 所以,布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。

3.布隆过滤器使用场景

  1. 判断给定数据是否存在。
  2. 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)等等、邮箱的垃圾邮件过滤、黑名单功能等等。。

4.用Java 实现布隆过滤器

  • 下面是我参考网上已有代码改的:
/**
 * 布隆过滤器
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/5/11
 */
public class BloomFilterDemo {
    /**
     * 位数组的大小
     */
    private static  int SIZE;
    /**
     * 通过这个数组可以创建不同的哈希函数
     */
    private static  int[] SEEDS;
    /**
     * 位数组。数组中的元素只能是 0 或者 1
     */
    private BitSet bits;

    /**
     * 存放包含 hash 函数的类的数组
     */
    private SimpleHash[] func;
    /**
     * 误判率
     */
    private MisjudgmentRate rate;
    /**
     * 自动清空
     */
    private  Double autoClearRate;
    /**
     * 使用数量
     */
    private final AtomicInteger useCount = new AtomicInteger(0);
    /**
     * 静态内部类。hash 函数
     */
    public static class SimpleHash {

        private int cap;
        private int seed;

        public SimpleHash(int cap, int seed) {
            this.cap = cap;
            this.seed = seed;
        }

        /**
         * 计算 hash 值
         */
        public int hash(Object value) {
            int h;
            return (value == null) ? 0 : Math.abs(seed * (cap - 1) & ((h = value.hashCode()) ^ (h >>> 16)));
        }

    }

    /**
     * 误判率
     */
    public enum MisjudgmentRate {
        /**
         * 每个字符串分配4个位
         */
        VERY_SMALL(new int[] { 2, 3, 5, 7 }),
        /**
         * 每个字符串分配8个位
         */
        SMALL(new int[] { 2, 3, 5, 7, 11, 13, 17, 19 }),
        /**
         * 每个字符串分配16个位
         */
        MIDDLE(new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 }),
        /**
         * 每个字符串分配32个位
         */
        HIGH(new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
                101, 103, 107, 109, 113, 127, 131 });

        private int[] seeds;

        private MisjudgmentRate(int[] seeds) {
            this.seeds = seeds;
        }

        public int[] getSeeds() {
            return seeds;
        }

        public void setSeeds(int[] seeds) {
            this.seeds = seeds;
        }

    }

    /**
     * 默认中等程序的误判率
     * @param dataCount 预期处理的数据规模,如预期用于处理1百万数据的查重,这里则填写1000000
     */
    public BloomFilterDemo(int dataCount){
        this(MisjudgmentRate.MIDDLE, dataCount, null);
    }

    /**
     *
     * @param rate 枚举类型的误判率
     * @param dataCount 预期处理的数据规模,如预期用于处理1百万数据的查重,这里则填写1000000
     * @param autoClearRate 自动清空过滤器内部信息的使用比率
     */
    public BloomFilterDemo(MisjudgmentRate rate, int dataCount, Double autoClearRate){
        long bitSize = rate.seeds.length * dataCount;
        if (bitSize < 0 || bitSize > Integer.MAX_VALUE) {
            throw new RuntimeException("位数太大溢出了,请降低误判率或者降低数据大小");
        }
        this.rate = rate;
        SEEDS = rate.seeds;
        SIZE = (int) bitSize;
        func = new SimpleHash[SEEDS.length];
        for (int i = 0; i < SEEDS.length; i++) {
            func[i] = new SimpleHash(SIZE, SEEDS[i]);
        }
        bits = new BitSet(SIZE);
        this.autoClearRate = autoClearRate;
    }

    /**
     * 添加元素到位数组
     */
    public void add(Object value) {

        checkNeedClear();

        if(!contains(value)){
	       for (SimpleHash f : func) {
	           bits.set(f.hash(value), true);
	       }
	       useCount.getAndIncrement();
        }
        
    }

    /**
     * 判断指定元素是否存在于位数组
     */
    public boolean contains(Object value) {
        boolean ret = true;
        for (SimpleHash f : func) {
            ret = ret && bits.get(f.hash(value));
        }
        return ret;
    }

    /**
     * 检查是否需要清空
     */
    private void checkNeedClear() {
        if (autoClearRate != null) {
            if (getUseRate() >= autoClearRate) {
                synchronized (this) {
                    if (getUseRate() >= autoClearRate) {
                        bits.clear();
                        useCount.set(0);
                    }
                }
            }
        }
    }
    public double getUseRate() {
        return (double) useCount.intValue() / (double) SIZE;
    }

    public static void main(String[] args) {
        String value1 = "fffdfg";
        String value2 = "ddbgbfgbfbbgfb";
        BloomFilterDemo filter = new BloomFilterDemo(2<<24);
        System.out.println(filter.contains(value1));
        System.out.println(filter.contains(value2));
        filter.add(value1);
        filter.add(value2);
        System.out.println(filter.contains(value1));
        System.out.println(filter.contains(value2));
        Integer value11 = 13423;
        Integer value21 = 22131;
        System.out.println(filter.contains(value11));
        System.out.println(filter.contains(value21));
        filter.add(value11);
        filter.add(value21);
        System.out.println(filter.contains(value11));
        System.out.println(filter.contains(value21));
    }

}
  • 运行结果:
false
false
true
true
false
false
true
true
  • 4
    点赞
  • 2
    评论
  • 8
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

wj-1024

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值