深入瞭解布隆過濾器

概念

布隆過濾器(英語:Bloom Filter)是1970年由一個叫布隆的小夥子提出的。它實際上是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器可以用於檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識別率和刪除困難。

原理

布隆過濾器的原理是,當一個元素被加入集合時,通過K個散列函數將這個元素映射成一個位數組中的K個點,把它們置爲1。檢索時,我們只要看看這些點是不是都是1就(大約)知道集合中有沒有它了:如果這些點有任何一個0,則被檢元素一定不在;如果都是1,則被檢元素很可能在。這就是布隆過濾器的基本思想。
Bloom Filter跟單哈希函數Bit-Map不同之處在於:Bloom Filter使用了k個哈希函數,每個字符串跟k個bit對應。從而降低了衝突的概率。

  • 集合裏面有3個元素,要把它存到布隆過濾器裏面去,應該怎麼做?首先是a元素,這裏我們用3次計算。b、c元素也一樣。
  • 元素已經存進去之後,現在我要來判斷一個元素在這個容器裏面是否存在,就要使用同樣的三個函數進行計算。
  • 比如d元素,我用第一個函數h1()計算,發現這個位置上是1,沒問題。第二個位置也是1,第三個位置也是1。
  • 如果經過三次計算得到的下標位置值都是1,這種情況下,能不能確定d元素一定 在這個容器裏面呢? 實際上是不能的。比如這張圖裏面,這三個位置分別是把a,b,c 存進去的時候置成1的,所以即使d元素之前沒有存進去,也會得到三個1,判斷返回 true。
  • 我們再來看另一個元素,e元素。我們要判斷它在容器裏面是否存在,一樣地要用這三個函數去計算。第一個位置是1,第二個位置是1,第三個位置是0。
  • e 元素是不是一定不在這個容器裏面呢? 可以確定一定不存在。如果說當時已經把e元素存到布隆過濾器裏面去了,那麼這三個位置肯定都是1,不可能出現0。

特點

從容器的角度來說:

  1. 如果布隆過濾器判斷元素在集合中存在,不一定存在
  2. 如果布隆過濾器判斷不存在,一定不存在

從元素的角度來說:

  1. 如果元素實際存在,布隆過濾器一定判斷存在
  2. 如果元素實際不存在,布隆過濾器可能判斷存在

缺點

bloom filter之所以能做到在時間和空間上的效率比較高,是因爲犧牲了判斷的準確率、刪除的便利性

  • 存在誤判,可能要查到的元素並沒有在容器中,但是hash之後得到的k個位置上值都是1。因爲哈希碰撞不可避免,所以它會存在一定的誤判率。這種把本來不存在布隆過濾器中的元素誤判爲存在的情況,我們把
    它叫做假陽性(False Positive Probability,FPP)。
  • 刪除困難。一個放入容器的元素映射到bit數組的k個位置上是1,刪除的時候不能簡單的直接置爲0,可能會影響其他元素的判斷。可以採用Counting Bloom Filter

實現

  • 在使用bloom filter時,繞不過的兩點是預估數據量n以及期望的誤判率fpp
  • 在實現bloom filter時,繞不過的兩點就是hash函數的選取以及bit數組的大小

對於一個確定的場景,我們預估要存的數據量爲n,期望的誤判率爲fpp,然後需要計算我們需要的Bit數組的大小m,以及hash函數的個數k,並選擇hash函數

Bit數組大小選擇

根據預估數據量n以及誤判率fpp,bit數組大小的m的計算方式:

位圖的容量是基於元素個數和誤判率計算出來的。

long numBits = optimalNumOfBits(expectedInsertions, fpp);

哈希函數選擇

由預估數據量n以及bit數組長度m,可以得到一個hash函數的個數k:

哈希函數的選擇對性能的影響應該是很大的,一個好的哈希函數要能近似等概率的將字符串映射到各個Bit。選擇k個不同的哈希函數比較麻煩,一種簡單的方法是選擇一個哈希函數,然後送入k個不同的參數。
哈希函數個數k、位數組大小m、加入的字符串數量n的關係可以參考Bloom Filters - the math,Bloom_filter-wikipedia
根據位數組的大小,我們進一步計算出了哈希函數的個數。

int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);

空間

存儲 100 萬個元素只佔用了 0.87M 的內存,生成了 5 個哈希函數。
https://hur.st/bloomfilter/?n=1000000&p=0.03&m=&k=

代碼

谷歌的 Guava 裏面就提供了一個現成的布隆過濾器。

<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>23.0</version>
</dependency>

簡單使用布隆過濾器:

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

public class BloomFilterTest {

    private static int total = 100000;
    private static BloomFilter<Integer> bf = BloomFilter.create(Funnels.integerFunnel(), total);
    //可以設置你允許的誤差率,誤差範圍:0<fpp<1
    //private static BloomFilter<Integer> bf = BloomFilter.create(Funnels.integerFunnel(), total, 0.01);

    public static void main(String[] args) {
        // 初始化100000條數據到過濾器中
        for (int i = 0; i < total; i++) {
            bf.put(i);
        }

        // 獲取隨機數匹配在過濾器中存在
        int i = (int)(1+Math.random()*(10000));
        if (bf.mightContain(i)) {
            System.out.println("BloomFilter 判定存在");
        }
    }
}

使用場景

  1. 如何在海量元素中快速判斷一個元素是否存在(Redis 緩存穿透)
  2. 爬蟲過濾已抓到的url
  3. 垃圾郵件過濾
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章