用Java實現布隆過濾器

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

        for (SimpleHash f : func) {
            bits.set(f.hash(value), true);
        }
    }

    /**
     * 判斷指定元素是否存在於位數組
     */
    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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章