Bloom Filter是1970年由Bloom提出的,最初廣泛用於拼寫檢查和數據庫系統中。近年來,隨着計算機和互聯網技術的發展,數據集的不斷擴張使得Bloom Filter獲得了新生,各種新的應用和變種不斷涌現。Bloom Filter是一個空間效率很高的隨機數據結構,它由一個位數組和一組hash映射函數組成。Bloom Filter可以用於檢索一個元素是否在一個集合中,它的優點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識別率。因此Bloom Filter不適合那些“零錯誤”的應用場合。而在能容忍低錯誤率的應用場合下,Bloom Filter通過極少的錯誤換取了存儲空間的極大節省。
(1)實例比較
假設要你寫一個網絡蜘蛛(web crawler)。由於網絡間的鏈接錯綜復雜,蜘蛛在網絡間爬行很可能會形成“環”。為了避免形成“環”,就需要知道蜘蛛已經訪問過那些URL。給一個URL,怎樣知道蜘蛛是否已經訪問過呢?稍微想想,就會有如下幾種方案:
1. 將訪問過的URL保存到數據庫。
2. 用HashSet將訪問過的URL保存起來。那只需接近O(1)的代價就可以查到一個URL是否被訪問過了。
3. URL經過MD5或SHA-1等單向哈希后再保存到HashSet或數據庫。
4. Bit-Map方法。建立一個BitSet,將每個URL經過一個哈希函數映射到某一位。
方法1~3都是將訪問過的URL完整保存,方法4則只標記URL的一個映射位。以上方法在數據量較小的情況下都能完美解決問題,但是當數據量變得非常龐大時問題就來了:
方法1的缺點:數據量變得非常龐大后關系型數據庫查詢的效率會變得很低。而且每來一個URL就啟動一次數據庫查詢是不是太小題大做了?
方法2的缺點:太消耗內存。隨着URL的增多,占用的內存會越來越多。就算只有1億個URL,每個URL只算50個字符,就需要5GB內存。
方法3:由於字符串經過MD5處理后的信息摘要長度只有128Bit,SHA-1處理后也只有160Bit,因此方法3比方法2節省了好幾倍的內存。
方法4消耗內存是相對較少的,但缺點是單一哈希函數發生沖突的概率太高。還記得數據結構課上學過的Hash表沖突的各種解決方法么?若要降低沖突發生的概率到1%,就要將BitSet的長度設置為URL個數的100倍。
實質上上面的算法都忽略了一個重要的隱含條件:允許小概率的出錯,不一定要100%准確!也就是說少量url實際上沒有沒網絡蜘蛛訪問,而將它們錯判為已訪問的代價是很小的——大不了少抓幾個網頁唄。
(2)Bloom Filter定義
Bloom Filter是一個有m位的位數組,初始全為0,並有k個各自獨立的哈希函數。
(注:Bloom Filter跟單哈希函數Bit-Map不同之處在於:Bloom Filter使用了k個哈希函數,每個字符串跟k個bit對應。從而降低了沖突的概率)
添加操作:
每個元素,用k個哈希函數計算出大小為k的哈希向量(h1,h2...hk),將向量里的每個哈希值對應的位設置為1。時間復雜度為o(n),一般字符串哈希函數的時間復雜度也就是o(n)。
查詢操作:和添加類似,先計算出哈希向量,如果每個哈希值對應的位都為1,則該元素存在。時間復雜度與添加操作相同。
(3)False Position
如果某元素不在Bloom Filter中,但是它所有哈希值的位置均被設為1。這種情況就是False Position,也就是誤判。Bloom Filter允許這種情況發生,且不可避免,我們關心的是False Position發生的概念,如何使其降低到最小。
1)哈希函數的選擇
哈希函數的選擇對性能的影響應該是很大的,一個好的哈希函數要能近似等概率的將字符串映射到各個Bit。選擇k個不同的哈希函數比較麻煩,一種簡單的方法是選擇一個哈希函數,然后送入k個不同的參數。
2)bit數組大小的選擇
哈希函數個數k、位數組大小m及字符串數量n之間存在相互關系。相關文獻證明了對於給定的m、n,當 k = ln(2)* m/n 時出錯的概率是最小的。
(4)優缺點
優點:
查詢操作十分高效
節省空間
易於擴展成並行
集合計算方便
代碼實現方便
缺點:
有誤判的概率,即存在False Position
無法獲取集合中的元素數據
不支持刪除操作(刪除會影響其他字符串)
- 注:對於不支持刪除操作,現在對其進行了擴展:Counting bloomfilter(CBF),這是一種基本Bloom Filter的變體,CBF將基本Bloom Filter每一個Bit改為一個計數器,這樣就可以實現刪除字符串的功能了。
(5)實際應用
1)加速查詢
適用於一些key-value存儲系統,當values存在硬盤時,查詢就是件費時的事。將Storage的數據都插入Filter,在Filter中查詢都不存在時,那就不需要去Storage查詢了。當False Position出現時,只是會導致一次多余的Storage查詢。其示意圖:

如:Google的BigTable也使用了Bloom Filter,以減少不存在的行或列在磁盤上的查詢,大大提高了數據庫的查詢操作的性能。
Internet Cache Protocol中的Proxy-Cache很多都是使用Bloom Filter存儲URLs,除了高效的查詢外,還能很方便得傳輸交換Cache信息。
2)網絡應用
P2P網絡中查找資源操作,可以對每條網絡通路保存Bloom Filter,當命中時,則選擇該通路訪問。
廣播消息時,可以檢測某個IP是否已發包。
檢測廣播消息包的環路,將Bloom Filter保存在包里,每個節點將自己添加入Bloom Filter。
信息隊列管理,使用Counter Bloom Filter管理信息流量。
3)垃圾郵件地址過濾
像網易,QQ這樣的公眾電子郵件(email)提供商,總是需要過濾來自發送垃圾郵件的人(spamer)的垃圾郵件。一個辦法就是記錄下那些發垃圾郵件的email 地址。由於那些發送者不停地在注冊新的地址,全世界少說也有幾十億個發垃圾郵件的地址,將他們都存起來則需要大量的網絡服務器。如果用哈希表,每存儲一億個 email 地址,就需要1.6GB 的內存(用哈希表實現的具體辦法是將每一個email 地址對應成一個八字節的信息指紋,然后將這些信息指紋存入哈希表,由於哈希表的存儲效率一般只有50%,因此一個email 地址需要占用十六個字節。一億個地址大約要1.6GB, 即十六億字節的內存)。因此存貯幾十億個郵件地址可能需要上百GB 的內存。而Bloom Filter只需要哈希表1/8 到1/4 的大小就能解決同樣的問題。Bloom Filter決不會漏掉任何一個在黑名單中的可疑地址。而至於誤判問題,常見的補救辦法是在建立一個小的白名單,存儲那些可能別誤判的郵件地址。
(6)代碼實現
代碼實現請見:http://blog.csdn.net/forestlight/article/details/6839180 ,該文章中給出了一個比較詳細的實現用例,其中有幾個難點:
1)Bloom Filter的創建函數BLOOM *bloom_create(size_t size, size_t nfuncs, ...);這是一個可變參數函數,里面涉及了可變參數的內容,如vg_list,vg_start,vg_arg,vg_end這些,可參考上一篇文章“關於C、C++中可變參數的簡介——(va_list,va_start,va_arg,va_end)”。
2) 關於typedef unsigned int (*hashfunc_t)(const char *); 這是一個函數類型的指針,可參考以前寫的“指針函數 vs 函數指針” 。
