Bloom Filter是由Bloom在1970年提出的一種快速查找算法,經過多個hash算法來共同判斷某個元素是否在某個集合內。能夠用於網絡爬蟲的url重複過濾、垃圾郵件的過濾等等。git
它相比hash容器的一個優點就是,不須要存儲元素的實際數據到容器中去來一個個的比較是否存在。 只須要對應的位段來標記是否存在就好了,因此想當節省內存,特別適合海量的數據處理。而且因爲省去了存儲元素和比較操做,因此性能也比基於hash容器的高了不少。github
可是因爲bloom filter沒有去比較元素,只經過多個hash來判斷惟一性,因此存在必定的hash衝突致使誤判。誤判率的大小由hash函數的個數、hash函數優劣、以及存儲的位空間大小共同決定。算法
而且刪除也比較困難,解決辦法是使用其變種,帶計數的bloom filter,這個這裏就很少說了。網絡
對於bloom filter算法的實現,至關簡單: 首先分配一塊固定的連續空間,大小是m個比特位(m/8+1個字節),而後再提供k個不一樣hash函數,同時對每一個元素進行計算位索引。若是每一個位索引對應的位都爲1,則存在該元素,不然就是不存在。dom
能夠看出,若是判斷爲不存在,那麼確定是不存在的,只有在判斷爲存在的時候,纔會存在誤判。函數
bloom filter主要的難點其實在於估算: 保證指定誤判率的狀況下,到底須要多少個hash函數,多少的存儲空間。性能
首先來看下bloom filter的誤判率計算公式:測試
假定有k個hash函數,m個比特位的存儲空間,n個集合元素,則有誤判率p:url
p = (1 - ((1 - 1/ m) ^ kn))^k ~= (1 - e^(-kn/m))^k
根據這個,官方給出了一個計算k的最優解公式,使其知足給定p的狀況下,存儲空間達到最小:spa
k = (m / n) * ln2
把它帶入機率公式獲得:
p = (1 - e ^-((m/nln2)n/m))^(m/nln2)
簡化爲:
lnp = -m/n * (ln2)^2
所以,若是指定p,只須要知足若是公式,就能夠獲得最優解:
s = m/n = -lnp / (ln2 * ln2) = -log2(p) / ln2 k = s * ln2 = -log2(p)
理論值:
p < 0.1: k = 3.321928, m/n = 4.79 p < 0.01: k = 6.643856, m/n = 9.58 p < 0.001: k = 9.965784, m/n = 14.37 p < 0.0001: k = 13.287712, m/n = 19.170117
能夠看出,這個確實可以在保證誤判率的前提下,使其存儲空間達到最小,可是使用的hash函數個數k 相對較多,至少也得4個,要知足p < 0.001,須要10個才行,這個對於字符串hash的計算來說,性能損耗至關大的,實際使用中根本無法接受。
所以咱們須要另一種推到公式,能夠認爲指定p和k的狀況下,來計算空間使用s=m/n的大小,這樣咱們在實際使用的時候,靈活性就大大提升了。
下面來看下,我本身推到出來的公式,首先仍是依據誤判率公式:
p = (1 - e^(-kn/m))^k
假定s=m/n,則有
p = (1 - e^(-k/s))^k
兩邊取導,獲得:
lnp = k * ln(1 - e^(-k/s))
交換k:
(lnp) / k = ln(1 - e^(-k/s))
從新上e:
e^((lnp) / k) = 1 - e^(-k/s)
化簡:
e^(-k/s) = 1 - e^((lnp) / k) = 1 - (e^lnp)^(1/k) = 1 - p^(1/k)
再求導:
-k/s = ln(1 - p^(1/k))
得出:
s = -k / ln(1 - p^(1/k))
假定c = p^(1/k)
:
s = -k / ln(1 - c)
利用泰勒展開式:ln(1 + x) ~= x - 0.5x^2 while x < 1
化簡獲得:
s ~= -k / (-c-0.5c^2) = 2k / (2c + c * c)
最後得出公式:
c = p^(1/k) s = m / n = 2k / (2c + c * c)
假定有n=10000000的數據量,則有理論值:
p < 0.1 and k = 1: s = m/n = 9.523810 p < 0.1 and k = 2: s = m/n = 5.461082 p < 0.1 and k = 3: s = m/n = 5.245850, space ~= 6.3MB p < 0.1 and k = 4: s = m/n = 5.552045, space ~= 6.6MB p < 0.01 and k = 1: s = m/n = 99.502488 p < 0.01 and k = 2: s = m/n = 19.047619 p < 0.01 and k = 3: s = m/n = 12.570636, space ~= 15MB p < 0.01 and k = 4: s = m/n = 10.922165, space ~= 13MB p < 0.001 and k = 1: s = m/n = 999.500250 p < 0.001 and k = 2: s = m/n = 62.261118 p < 0.001 and k = 3: s = m/n = 28.571429, space ~= 34MB p < 0.001 and k = 4: s = m/n = 20.656961, space ~= 24.6MB p < 0.0001 and k = 1: s = m/n = 9999.500025 p < 0.0001 and k = 2: s = m/n = 199.004975 p < 0.0001 and k = 3: s = m/n = 63.167063, space ~= 75.3MB p < 0.0001 and k = 4: s = m/n = 38.095238, space ~= 45.4MB p < 0.0001 and k = 5: s = m/n = 29.231432, space ~= 24.8MB
能夠看到,在k=3的狀況下,其實已經能夠達到咱們日常使用所能的接受範圍內了,不必非得 使用最優解,除非在空間使用極爲苛刻的狀況下,並且這個公式,針對程序空間使用的調整,更加的靈活智能。
特別提下,通過實測,若是每一個hash的實現很是優質,分佈很均勻的狀況下,其實際的誤判率比理論值低不少:
就拿TBOX的bloom filter的實現作測試,n=10000000:
p < 0.01 and k = 3的狀況下,其實際誤判率爲:0.004965 p < 0.001 and k = 3的狀況下,其實際誤判率爲:0.000967
因此好的hash函數算法也是尤其的重要。
下面來看下TBOX提供的bloom filter的使用,用起來也是至關的方便: <!--lang:cpp--> // 總的元素個數 tb_size_t count = 10000000;
/* 初始化bloom filter * * TB_BLOOM_FILTER_PROBABILITY_0_01: 預約義的誤判率,接近0.01 * 注:因爲內部使用位移數來表示:1 / 2^6 = 0.015625 ~= 0.01 * 因此實際傳入的誤判率,有可能稍微大一點,可是仍是至關接近的 * * 3:爲k值,hash函數的個數,最大不超過15個 * * count:指定的元素規模數 * * tb_item_func_long():容器的元素類型,主要是用其內定的hash函數,若是要自定義hash函數,能夠替換: * * tb_size_t tb_xxxxxx_hash(tb_item_func_t* func, tb_cpointer_t data, tb_size_t mask, tb_size_t index) * { * // mask爲hash掩碼,index爲第index個hash算法的索引 * return compute_hash(data, index) & mask; * } * * tb_item_func_t func = tb_item_func_long(); * func.hash = tb_xxxxxx_hash; * * 來進行 */ tb_bloom_filter_ref_t filter = tb_bloom_filter_init(TB_BLOOM_FILTER_PROBABILITY_0_01, 3, count, tb_item_func_long()); if (filter) { tb_size_t i = 0; for (i = 0; i < count; i++) { // 產生隨機數 tb_long_t value = tb_random(); // 設置值到filter內,若是不存在,則返回tb_true表示設置成功 if (tb_bloom_filter_set(filter, (tb_cpointer_t)value)) { // 添加元素成功,以前元素不存在 // 不會存在誤判 } else { // 添加失敗,添加的元素已經存在 // 這裏可能會存在誤判 } // 僅僅判斷元素是否存在 if (tb_bloom_filter_get(filter, (tb_cpointer_t)data) { // 元素已經存在 // 這裏可能會存在誤判 } else { // 元素不存在 // 不會存在誤判 } } // 退出filter tb_bloom_filter_exit(filter); } // 經常使用預約義的誤判率,也能夠指定其餘值,注:必須是位移數,而不是實際值 typedef enum __tb_bloom_filter_probability_e { TB_BLOOM_FILTER_PROBABILITY_0_1 = 3 ///!< 1 / 2^3 = 0.125 ~= 0.1 , TB_BLOOM_FILTER_PROBABILITY_0_01 = 6 ///!< 1 / 2^6 = 0.015625 ~= 0.01 , TB_BLOOM_FILTER_PROBABILITY_0_001 = 10 ///!< 1 / 2^10 = 0.0009765625 ~= 0.001 , TB_BLOOM_FILTER_PROBABILITY_0_0001 = 13 ///!< 1 / 2^13 = 0.0001220703125 ~= 0.0001 , TB_BLOOM_FILTER_PROBABILITY_0_00001 = 16 ///!< 1 / 2^16 = 0.0000152587890625 ~= 0.00001 , TB_BLOOM_FILTER_PROBABILITY_0_000001 = 20 ///!< 1 / 2^20 = 0.00000095367431640625 ~= 0.000001 }tb_bloom_filter_probability_e;