深度剖析各種BloomFilter的原理、改進、應用場景


  Bloom Filter是由Bloom在1970年提出的一種多哈希函數映射的快速查找算法。通常應用在一些需要快速判斷某個元素是否屬於集合,但是並不嚴格要求100%正確的場合

 

實例 

  為了說明Bloom Filter存在的重要意義,舉一個實例:

  假設要你寫一個網絡蜘蛛(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實際上沒有沒網絡蜘蛛訪問,而將它們錯判為已訪問的代價是很小的——大不了少抓幾個網頁唄。 

 

. Bloom Filter的算法 

 

  廢話說到這里,下面引入本篇的主角——Bloom Filter。其實上面方法4的思想已經很接近Bloom Filter了。方法四的致命缺點是沖突概率高,為了降低沖突的概念,Bloom Filter使用了多個哈希函數,而不是一個。

    Bloom Filter算法如下:

    創建一個m位BitSet,先將所有位初始化為0,然后選擇k個不同的哈希函數。第i個哈希函數對字符串str哈希的結果記為h(i,str),且h(i,str)的范圍是0到m-1 。

 

(1) 加入字符串過程 

 

  下面是每個字符串處理的過程,首先是將字符串str“記錄”到BitSet中的過程:

  對於字符串str,分別計算h(1,str),h(2,str)…… h(k,str)。然后將BitSet的第h(1,str)、h(2,str)…… h(k,str)位設為1。

 

  圖1.Bloom Filter加入字符串過程

  很簡單吧?這樣就將字符串str映射到BitSet中的k個二進制位了。

 

(2) 檢查字符串是否存在的過程 

 

  下面是檢查字符串str是否被BitSet記錄過的過程:

  對於字符串str,分別計算h(1,str),h(2,str)…… h(k,str)。然后檢查BitSet的第h(1,str)、h(2,str)…… h(k,str)位是否為1,若其中任何一位不為1則可以判定str一定沒有被記錄過。若全部位都是1,則“認為”字符串str存在。

 

  若一個字符串對應的Bit不全為1,則可以肯定該字符串一定沒有被Bloom Filter記錄過。(這是顯然的,因為字符串被記錄過,其對應的二進制位肯定全部被設為1了)

  但是若一個字符串對應的Bit全為1,實際上是不能100%的肯定該字符串被Bloom Filter記錄過的。(因為有可能該字符串的所有位都剛好是被其他字符串所對應)這種將該字符串划分錯的情況,稱為false positive

 

(3) 刪除字符串過程 

   字符串加入了就被不能刪除了,因為刪除會影響到其他字符串。實在需要刪除字符串的可以使用Counting bloomfilter(CBF),這是一種基本Bloom Filter的變體,CBF將基本Bloom Filter每一個Bit改為一個計數器,這樣就可以實現刪除字符串的功能了。

 

  Bloom Filter跟單哈希函數Bit-Map不同之處在於:Bloom Filter使用了k個哈希函數,每個字符串跟k個bit對應。從而降低了沖突的概率。

 

. Bloom Filter參數選擇 

 

 

 m是bit位數,k是函數個數。 m/n越大越准,k越大越准

 

   (1)哈希函數選擇

     哈希函數的選擇對性能的影響應該是很大的,一個好的哈希函數要能近似等概率的將字符串映射到各個Bit。選擇k個不同的哈希函數比較麻煩,一種簡單的方法是選擇一個哈希函數,然后送入k個不同的參數。

   (2)Bit數組大小選擇 

     哈希函數個數k、位數組大小m、加入的字符串數量n的關系可以參考參考文獻1。該文獻證明了對於給定的m、n,當 k = ln(2)* m/n 時出錯的概率是最小的

     同時該文獻還給出特定的k,m,n的出錯概率。例如:根據參考文獻1,哈希函數個數k取10,位數組大小m設為字符串個數n的20倍時,false positive發生的概率是0.0000889 ,這個概率基本能滿足網絡爬蟲的需求了。  

 

. Bloom Filter實現代碼 

    下面給出一個簡單的Bloom Filter的Java實現代碼:

 

復制代碼
import java.util.BitSet;

publicclass BloomFilter
{
/* BitSet初始分配2^24個bit */
privatestaticfinalint DEFAULT_SIZE =1<<25;
/* 不同哈希函數的種子,一般應取質數 */
privatestaticfinalint[] seeds =newint[] { 5, 7, 11, 13, 31, 37, 61 };
private BitSet bits =new BitSet(DEFAULT_SIZE);
/* 哈希函數對象 */
private SimpleHash[] func =new SimpleHash[seeds.length];

public BloomFilter()
{
for (int i =0; i < seeds.length; i++)
{
func[i] =new SimpleHash(DEFAULT_SIZE, seeds[i]);
}
}

// 將字符串標記到bits中
publicvoid add(String value)
{
for (SimpleHash f : func)
{
bits.set(f.hash(value), true);
}
}

//判斷字符串是否已經被bits標記
publicboolean contains(String value)
{
if (value ==null)
{
returnfalse;
}
boolean ret =true;
for (SimpleHash f : func)
{
ret = ret && bits.get(f.hash(value));
}
return ret;
}

/* 哈希函數類 */
publicstaticclass SimpleHash
{
privateint cap;
privateint seed;

public SimpleHash(int cap, int seed)
{
this.cap = cap;
this.seed = seed;
}

//hash函數,采用簡單的加權和hash
publicint 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;
}
}
}
復制代碼

 

 五. 變種之Counting Bloom Filter

除了存在false positive這個問題之外,傳統的Bloom Filter還有一個不足:無法支持刪除操作(想想看,是不是這樣的)。而Counting Bloom Filter(CBF)就是用來解決這個問題的。

在CBF中,維護的不是單純的標示0或者1的比特位,而是計數器counter。對於集合中的每個元素,利用k個哈希函數,對它哈希得到k個位置,將對應的k個位置上的k個counter都加1。刪除時,只需要把k個counter都減1即可。

那么,這個counter應該占用幾位呢?分配太多,浪費空間;分配太少,容易溢出。通過下面的分析,我們可以知道,實際使用時,4位足矣。

考察(是考察,不是考查。這兩個詞有什么區別?)某個位置,該位置的計數器counter的值ξξ

P(ξ=c)(mkc)(1n)c(11n)mkc=B(km,1n)P(ξ=c)≈(mkc)(1n)c(1−1n)mk−c=B(km,1n)

這個式子有點點復雜,然而回憶下概率論里的知識:若二項分布B(n,p)里n很大,p很小時,二項分布的極限近似分布是泊松分布P(λ=k)=λkk!eλP(λ=k)=λkk!e−λ,其中λ=npλ=np,因此:

P(ξ=c)(mkc)(1n)c(11n)mkc(kmn)cc!ekmnP(ξ=c)≈(mkc)(1n)c(1−1n)mk−c≈(kmn)cc!e−kmn

k=nmln2k=nmln2,代入,我們得到

P(ξ>16)(ln2)1616!12<116!=120922789888000P(ξ>16)≈(ln2)1616!∗12<116!=120922789888000

也就是說,選擇4位來存counter在實際情況中已經足矣,發生溢出的概率極小。

分支算法, 

  • d-left hashing
  • d-left counting bloom filter

參考:http://www.yebangyu.org/blog/2016/01/29/insidethebloomfilter/

六. 其他變種

Compressed Bloom Filter

Deletable Bloom filter

Hierarchical Bloom Filters

Spectral Bloom Filters

Bloomier Filters

Decaying Bloom Filters

Stable Bloom Filter

 Space Code Bloom Filter

Filter Banks

Scalable Bloom filters

 Split Bloom Filters

Retouched Bloom filters

Generalized Bloom Filters

Distance-sensitive Bloom filters

Data Popularity Conscious Bloom Filters

Memory-optimized Bloom Filter

Weighted Bloom filter

Secure Bloom filters

 

對比

 

 

 

參考文獻:

 

[1]Pei Cao. Bloom Filters - the math.

http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html

[2]Wikipedia. Bloom filter.

http://en.wikipedia.org/wiki/Bloom_filter

http://www.dca.fee.unicamp.br/~chesteve/pubs/bloom-filter-ieee-survey-preprint.pdf


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM