Bloom Filter一般用於數據的去重計算,近似於HashSet的功能;但是不同於Bitmap(用於精確計算),其為一種估算的數據結構,存在誤判(false positive)的情況。
1. 基本原理
Bloom Filter能高效地表征數據集合\(S = \lbrace x_1 ,x_2 ,...,x_n \rbrace\),判斷某個數據是否屬於這個集合。其基本思想如下:用長度為\(m\)的位數組\(A\)來存儲集合信息,同時是有\(k\)個獨立的hash函數\(h_i(1\le i \le k)\)將數據映射到位數組空間。具體流程如下:
- 將長度為\(m\)的位數組全置為0;
- 對於數據\(x \in S\),依次計算其\(k\)個hash函數值\(h_i(x)=w,且1\le i \le k, 1 \le w \le m\),將位數組中的第\(a\)位bit置為1,即A[w]=1.
當查詢數據\(y\)是否屬於集合\(S\)時,計算其\(k\)個hash函數值,如果\(h_i(y)\)對應的位數組均為1,則數據\(y\)屬於集合\(S\);反之,則不屬於。
2. 相關計算
在上述判斷中,可能存在誤判(false positive, FP),比如某數的\(k\)個hash函數值可能屬於集合\(S\)中某幾個數\(k\)個hash函數值組成的集合。顯然,誤判率跟集合大小\(n\)、位數組大小\(m\)、hash函數的個數\(k\)有關;在其他條件不變的情況下,若\(n\)越大(\(m\)越小,或\(k\)越多),則誤判率越高。誤判率估算公式如下:
在實際的場景中,常常是已知集合大小\(n\),預設誤判率\(P_{fp}\),需要計算位數組大小\(m\)、hash函數的個數\(k\)。通過一系列的數學推導,可得到如下公式:
詳細的數學推導可參看相關文檔。
3. 實戰
Bloom Filter的Java實現有Guava、stream-lib,Scala實現有breeze、bloom-filter-scala。采用breeze庫的Distinct Count實現如下:
import breeze.util.BloomFilter
val bf = BloomFilter.optimallySized[Int](5, 0.01)
val arr = Array(1, 3, 4, 5, 1, 2, 6, 3, 1)
var cnt = 0
arr.foreach { t =>
bf.contains(t) match {
case false => cnt += 1; bf.+=(t)
case _ =>
}
}
println(arr.distinct.length) // 6
println(cnt) // 6
從上面的Scala代碼中,不難發現:在Distinct Count計算過程中,需要定義一個global變量,逐一用於對每個不屬於集合元素進行計算。顯然,在分布式計算中,這種方法不太適用;因為global變量沒法做到實時的傳遞更新。因此,另一種估算算法HyperLogLog,擁有優秀的可加性、易於並行化,在大數據的場景下應用廣泛——Spark、Kylin中的近似Distinct Count便是基於此。
4. 參考資料
[1] Broder, Andrei, and Michael Mitzenmacher. "Network Applications of Bloom Filters: A Survey." Internet Mathematics 1.4 (2011): 485-509.
[2] 張俊林, 《大數據日知錄》.