天天看點

布隆過濾器布隆過濾器原理

布隆過濾器原理

布隆過濾器有什麼用?

布隆過濾器是可以用于判斷一個元素是不是在一個集合裡,并且相比于其它的資料結構,布隆過濾器在空間和時間方面都有巨大的優勢。

特點:

  • 巴頓.布隆于一九七零年提出
  • 一個很長的二進制向量 (位數組)
  • 一系列随機函數 (哈希)
  • 空間效率和查詢效率高:O(1)
  • 有一定的誤判率(哈希表是精确比對)

實作原理

布隆過濾器(Bloom Filter)的核心實作是一個超大的位數組和幾個哈希函數。假設位數組的長度為m,哈希函數的個數為k

布隆過濾器布隆過濾器原理

以上圖為例,具體的操作流程:假設集合裡面有3個元素{x, y, z},哈希函數的個數為3(這裡元素個數和哈希函數的數量沒有直接關系)。

首先将位數組進行初始化,将裡面每個位都設定位0。

對于集合裡面的每一個元素,将元素依次通過3個哈希函數進行映射,每次映射都會産生一個哈希值,這個值對應位數組上面的一個點,然後将位數組對應的位置标記為1。

查詢W元素是否存在集合中的時候,同樣的方法将W通過哈希映射到位數組上的3個點。如果3個點的其中有一個點不為1,則可以判斷該元素==一定==不存在集合中。反之,如果3個點都為1,則該元素==可能==存在集合中。

注意:此處不能判斷該元素是否一定存在集合中,可能存在一定的==誤判率==。可以從圖中可以看到:假設某個元素通過映射對應下标為4,5,6這3個點。雖然這3個點都為1,但是很明顯這3個點是不同元素經過哈希得到的位置,是以這種情況說明元素雖然不在集合中,也可能對應的都是1,這是誤判率存在的原因。

java代碼實作

可以看下面這段代碼,也可以到

https://archive.codeplex.com/?p=bloomfilter

這個位址看開源代碼

import java.io.Serializable;
import java.util.BitSet;
import java.util.concurrent.atomic.AtomicInteger;
 
public class BloomFileter implements Serializable {
    private static final long serialVersionUID = -5221305273707291280L;
    private final int[] seeds;
    private final int size;
    private final BitSet notebook;
    private final MisjudgmentRate rate;
    private final AtomicInteger useCount = new AtomicInteger(0);
    private final Double autoClearRate;
 
    /**
     * 預設中等程式的誤判率:MisjudgmentRate.MIDDLE 以及不自動清空資料(性能會有少許提升)
     *
     * @param dataCount 預期處理的資料規模,如預期用于處理1百萬資料的查重,這裡則填寫1000000
     */
    public BloomFileter(int dataCount) {
        this(MisjudgmentRate.MIDDLE, dataCount, null);
    }
 
    /**
     * @param rate          一個枚舉類型的誤判率
     * @param dataCount     預期處理的資料規模,如預期用于處理1百萬資料的查重,這裡則填寫1000000
     * @param autoClearRate 自動清空過濾器内部資訊的使用比率,傳null則表示不會自動清理,
     *                      當過濾器使用率達到100%時,則無論傳入什麼資料,都會認為在資料已經存在了
     *                      當希望過濾器使用率達到80%時自動清空重新使用,則傳入0.8
     */
    public BloomFileter(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;
        notebook = new BitSet(size);
        this.autoClearRate = autoClearRate;
    }
 
    public void add(String data) {
        checkNeedClear();
 
        for (int i = 0; i < seeds.length; i++) {
            int index = hash(data, seeds[i]);
            setTrue(index);
        }
    }
 
    /**
     * 如果不存在就進行記錄并傳回false,如果存在了就傳回true
     *
     * @param data
     * @return
     */
    public boolean addIfNotExist(String data) {
        checkNeedClear();
 
        int[] indexs = new int[seeds.length];
        // 先假定存在
        boolean exist = true;
        int index;
 
        for (int i = 0; i < seeds.length; i++) {
            indexs[i] = index = hash(data, seeds[i]);
 
            if (exist) {
                if (!notebook.get(index)) {
                    // 隻要有一個不存在,就可以認為整個字元串都是第一次出現的
                    exist = false;
                    // 補充之前的資訊
                    for (int j = 0; j <= i; j++) {
                        setTrue(indexs[j]);
                    }
                }
            } else {
                setTrue(index);
            }
        }
 
        return exist;
 
    }
 
    private void checkNeedClear() {
        if (autoClearRate != null) {
            if (getUseRate() >= autoClearRate) {
                synchronized (this) {
                    if (getUseRate() >= autoClearRate) {
                        notebook.clear();
                        useCount.set(0);
                    }
                }
            }
        }
    }
 
    public void setTrue(int index) {
        useCount.incrementAndGet();
        notebook.set(index, true);
    }
 
    private int hash(String data, int seeds) {
        char[] value = data.toCharArray();
        int hash = 0;
        if (value.length > 0) {
 
            for (int i = 0; i < value.length; i++) {
                hash = i * hash + value[i];
            }
        }
 
        hash = hash * seeds % size;
        // 防止溢出變成負數
        return Math.abs(hash);
    }
 
    public double getUseRate() {
        return (double) useCount.intValue() / (double) size;
    }
 
    /**
     * 配置設定的位數越多,誤判率越低但是越占記憶體
     * <p>
     * 4個位誤判率大概是0.14689159766308
     * <p>
     * 8個位誤判率大概是0.02157714146322
     * <p>
     * 16個位誤判率大概是0.00046557303372
     * <p>
     * 32個位誤判率大概是0.00000021167340
     *
     * @author lianghaohui
     */
    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;
        }
 
    }
 
    public static void main(String[] args) {
        BloomFileter fileter = new BloomFileter(7);
        System.out.println(fileter.addIfNotExist("1111111111111"));
        System.out.println(fileter.addIfNotExist("2222222222222222"));
        System.out.println(fileter.addIfNotExist("3333333333333333"));
        System.out.println(fileter.addIfNotExist("444444444444444"));
        System.out.println(fileter.addIfNotExist("5555555555555"));
        System.out.println(fileter.addIfNotExist("6666666666666"));
        System.out.println(fileter.addIfNotExist("1111111111111"));
    }
}           

錯誤率估算

純數學算法推導,公式參見:布隆過濾器 (Bloom Filter) 詳解

下面給出一個直覺的圖:

  • m:存儲比特位的數組長度(數組長度越長,元素越小,則誤判幾率越低)注意:m必須>n,不然當隻有一個哈希函數的時候都一定會出現hash沖突
  • n:需要存儲轉換的元素的個數
  • K:把元素M映射在數組上哪一位為1的哈希函數的個數。 K要 <= m/n
布隆過濾器布隆過濾器原理