布隆過濾器
布隆過濾器概述
布隆過濾器解決這樣一個問題,假設有一個搜尋引擎公司,在他公司的伺服器上,有100億條URL黑名單,當你搜索某個URL的時候,伺服器就會檢測這些URL在不在黑名單,如果在,就不顯示,如果不在,就顯示
首先算一下這個100億的URL佔多大儲存空間,假設一個URL是64位元組,算下來總共大概640GB
要求:
- 該系統允許有萬分之一以下的失誤率
- 使用的額外空間不能超過30GB
雜湊函式
在瞭解布隆過濾實現之前先認識雜湊函式
雜湊函式的性質:
- 經典雜湊函式的輸入域無窮大
- 輸出域有窮
- 雜湊函式沒有隨機值。★即輸入一樣,輸出也一樣★
- 因為輸入域無窮大,輸出域有窮,所以必定存在多個輸入對應一個輸出
- 由於雜湊函式的離散性,所有的輸入總是“均勻的”分佈在所有的輸出中。例如:有0~98個輸入,輸出 0~2。在0位置有33個左右的輸入,其他也相同
- 離散性的推論:當所有的輸入對應的輸出都模一個數m,那麼在輸出0~m-1上也是“均勻分佈的”
布隆過濾器
如果把黑名單中所有的URL通過資料庫或雜湊表儲存下來,就可以對每條URL進行查詢,但是至少需要640GB的空間,不滿足要求
如果遇到網頁黑名單系統、垃圾郵件過濾系統、爬蟲的網址判重系統等問題,又看到系統容忍一定程度的失誤率,但是對空間要求比較嚴格,那麼很可能就需要用到布隆過濾器。一個布隆過濾器精確地代表一個集合,並可以精確判斷一個元素是否在集合中。注意,只是精確代表和精確判斷,到底有多少精確呢?則完全在於你具體的設計,但想做到完全正確是不可能的。布隆過濾器的優勢在於使用很少的空間就可以將準確率做到很高的程度。
假設有一個長度為m的bit型別的陣列,即陣列中的每一個位置只佔一個bit,每一個bit只有0和1兩種狀態。再假設有k個雜湊函式,這些函式的輸出域S都大於或等於m,並且這些雜湊函式都足夠優秀,彼此之間也完全獨立。那麼對同一個輸入物件(假設是一個字串記為URL),經過k個雜湊函式算出來的結果也是獨立的,可能相同,也可能不相同,但彼此獨立。對算出來的每一個結果都對m取餘(%m),然後在bit陣列上把相應的位置設定為1(塗黑)
把bit型別的陣列記為bitMap。至此,一個輸入物件對bitMap的影響過程就結束了,也就是bitMap中的一些未知會被塗黑。接下來按照該方法處理所有的輸入物件,每個物件都可能把bitMap中的一些白位置塗黑,也可能遇到已經塗黑的位置,遇到已經塗黑的位置讓其繼續為黑即可。處理完所有的輸入物件後,可能bitMap中已經有相當多的位置被塗黑。至此,一個布隆過濾器生成完畢,這個布隆過濾器代表之前所有輸入物件組成的集合
如何檢查某一個物件是否是之前的某一個輸入物件呢?假設一個物件為a,想檢查它是否是之前的輸入物件,就把a通過k個雜湊函式算出k個值,然後把k個值取餘(%m),就得到在[0,m-1]範圍上的k個值。接下來在bitMap上看這些位置是不是都為黑。如果有一個不為黑,說明a一定不在這個集合裡。如果都為黑,說明a在這個集合裡,但可能有誤判。具體一點,如果a的確是輸入物件,那麼在生成布隆過濾器時,bitMap中相應的k個位置一定已經塗黑了,所以在檢查階段,a一定不會被漏過,這個不會產生誤判。會產生誤判的是,a明明不是輸入物件,但如果在生成布隆過濾器的階段因為輸入物件太多,而bitMap過小,則會導致bitMap絕大多數的位置都已經變黑。那麼在檢查a時,可能a對應的k個位置都是黑的,從而錯誤地認為a是輸入物件。通俗地說,布隆過濾器的失誤型別是“寧可錯殺三千,絕不放過一個”。使用布隆過濾器的另一個好處是不用顧忌單個樣本的大小,它絲毫不會影響布隆過濾器的大小
如果bitMap的大小m相對於輸入物件的個數n過小,失誤率會變大。根據n的大小和想要達到的失誤率p,如何確定布隆過濾器的大小m和雜湊函式的個數k,最後是布隆過濾器的失誤率分析
布隆過濾器失誤率分析
黑名單中樣本的個數為100億個,記為$n$;失誤率不能超過0.01%,記為$p$;每個樣本的大小為64B,這個資訊不會影響布隆過濾器的大小,只和選擇雜湊函式有關,一般的雜湊函式都可以接收64B的輸入物件
所以$n=100億$,$p=0.01%$,布隆過濾器的大小m由以下公式確定:
$$
m = -\frac{n*lnp}{(ln2)^2}
$$
根據公式計算出$m = 19.19n$,向上取整為$20n$,即需要2000億個bit,也就是25GB
雜湊函式的個數$k$由以下公式確定:
$$
k = ln2*\frac{m}{n} = 0.7*\frac{m}{n}
$$
計算出雜湊函式的個數為$k = 14$個
用25GB的bitMap再加上單獨實現的14個雜湊函式,根據如上描述生成布隆過濾器即可。因為我們在確定布隆過濾器大小的過程中選擇了向上取整,所以還要用如下公式確定布隆過濾器真實的失誤率為:
$$
p = (1 - e^{-\frac{nk}{m}})^k
$$
根據這個公式算出真實的失誤率為$0.006%$,這是比$0.01%$更低的失誤率,雜湊函式本身不佔用什麼空間,所以使用空間就是bitMap的大小(即25GB),伺服器的記憶體都可以達到這個級別,所有要求達標。
程式碼
/** * 布隆過濾器 */ public class SimpleBloomFilter { private static final int DEFAULT_SIZE = 2 << 24; private static final int[] seeds = new int[]{7, 11, 13, 31, 37, 61}; private BitSet bits = new BitSet(DEFAULT_SIZE); private SimpleHash[] func = new SimpleHash[seeds.length]; public SimpleBloomFilter() { for (int i = 0; i < seeds.length; i++) { func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]); } } public void add(String value) { for (SimpleHash f : func) { bits.set(f.hash(value), true); } } public boolean contains(String value) { if (value == null) { return false; } boolean ret = true; for (SimpleHash f : func) { ret = ret && bits.get(f.hash(value)); } return ret; } public static class SimpleHash { private int cap; private int seed; public SimpleHash(int cap, int seed) { this.cap = cap; this.seed = seed; } public int hash(String value) { int result = 0; int len = value.length(); for (int i = 0; i < len; i++) { result = seed * result + value.charAt(i); } return (cap - 1) & result; } } public static void main(String[] args) { String value = "[email protected]"; SimpleBloomFilter filter = new SimpleBloomFilter(); System.out.println(filter.contains(value)); filter.add(value); System.out.println(filter.contains(value)); } }