BloomFilter 與 CuckooFilter
Bloom Filter 原理
Bloom Filter是一種空間效率很高的隨機數據結構,它的原理是,當一個元素被加入集合時,通過K個相互獨立的Hash函數將這個元素映射成一個位陣列(Bit array)中的K個點,把它們置為1。檢索時,我們只要看看這些點是不是都是1就(大約)知道集合中有沒有它了;如果這些點有任何一個0,則被檢索元素一定不在;如果都是1,則被檢索元素很可能在。
Bloom Filter的這種高效是有一定代價的,在判斷一個元素是否屬於某個集合時,有可能會把不屬於這個集合的元素誤認為屬於這個集合(false positive)。因此,並不適合那些“零錯誤”的應用場合。而在能容忍低錯誤率的應用場合下,Bloom Filter通過極少的錯誤換取了存儲空間的極大節省。
假設要你寫一個網絡爬蟲程序(web crawler)。由於網絡間的鏈接錯綜復雜,爬蟲在網絡間爬行很可能會形成“環”。為了避免形成“環”,就需要知道爬蟲程序已經訪問過那些URL。給一個URL,怎樣知道爬蟲程序是否已經訪問過呢?稍微想想,就會有如下幾種方案:
- 將訪問過的URL保存到數據庫。
- 用HashSet將訪問過的URL保存起來。那只需接近O(1)的代價就可以查到一個URL是否被訪問過了。
- URL經過MD5或SHA-1等單向哈希后再保存到HashSet或數據庫。
- 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倍。Bloom Filter 與單哈希函數Bit-Map不同之處在於:Bloom Filter使用了k個哈希函數,每個字符串跟k個bit對應。從而降低了沖突的概率。
創建一個m位BitSet,先將所有位初始化為0,然后選擇k個不同的哈希函數。第 i 個哈希函數對字符串str哈希的結果記為Hi(str),並且滿足:
0 <= Hi(str) < m (1<=i<=k)
(1) 將字符串 str 映射到BitSet中的過程:分別計算H1(str),H2(str),…,Hk(str),然后在BitSet中將對應的位置1。
(2) 檢查字符串str是否被BitSet記錄過的過程:分別計算H1(str),H2(str),…,Hk(str),然后在BitSet中對應的位檢查是否為1。若其中任何一位不為1則可以判定str一定沒有被記錄過。若全部位都是1,則認為字符串str存在。注意:這里也可能存在誤判,因為有可能該字符串的所有位都剛好是被其他字符串所對應,這種將該字符串划分錯的情況稱為false positive 。
(3) 刪除字符串過程,字符串加入了就被不能刪除了,因為刪除會影響到其他字符串。
實在需要刪除字符串的可以使用Counting Bloom Filter (CBF),這是一種基本Bloom Filter的變體,CBF將基本Bloom Filter每一個Bit改為一個計數器,這樣就可以實現刪除字符串的功能了。
Bloom Filter 參數選擇
問題:m(bit-map位數), n(待處理的字符串個數), k(哈希函數個數)值,我們該如何取值呢?
當hash函數個數 k = (ln2) * (m/n) 時錯誤率最小。
在錯誤率不大於e的情況下,則m >= n*log2(1/e)*log2e 。
這里直接給出了結論,如果對上述公式推導過程感興趣,可以參考這里。
舉個例子我們假設錯誤率為0.001,則此時m應大概是n的14倍。這樣k大概是4個。
Bloom Filter 應用
最后,總結下Bloom Filter 的優點:
- 節約緩存空間(空值的映射),不再需要空值映射;
- 減少數據庫或緩存的請求次數;
- 提升業務的處理效率以及業務隔離性。
缺點:
- 存在誤判的概率;
- 傳統的Bloom Filter不能作刪除操作(可以使用CBF來支持刪除功能)。
Bloom Filter 可以用來實現數據字典,進行數據的判重,或者集合求交集 。
Cuckoo 布谷鳥哈希
前面提到,Bloom Filter 可能存在誤報,並且無法刪除元素,而Cuckoo哈希就是解決這兩個問題的。
Cuckoo的哈希函數是成對的(具體的實現可以根據需求設計),每一個元素都是兩個,分別映射到兩個位置,一個是記錄的位置,另一個是備用位置,這個備用位置是處理碰撞時用的。
如下圖,使用hashA 和hashB 計算對應key x的位置a和b :
- 當兩個哈希位置有一個為空時,則插入該空位置;
- 當兩個哈希位置均不為空時,隨機選擇兩者之一的位置上key y 踢出,並計算踢出的key y在另一個哈希值對應的位置,若為空直接插入,不為空踢出原元素插入,再對被踢出的元素重新計算,重復該過程,直到有空位置為止。
Cockoo hashing 有兩種變形:一種通過增加哈希函數進一步提高空間利用率;另一種是增加哈希表,每個哈希函數對應一個哈希表,每次選擇多個張表中空余位置進行放置,三個哈希表可以達到80% 的空間利用率。
Cockoo hashing 的過程可能因為反復踢出無限循環下去,這時候就需要進行一次循環踢出的限制,超過限制則認為需要添加新的哈希函數。
參考文檔:
http://blog.csdn.net/v_july_v/article/details/6685894/
http://blog.csdn.net/v_july_v/article/details/7382693
https://github.com/jaybaird/python-bloomfilter/blob/master/pybloom/pybloom.py
http://coolshell.cn/articles/17225.html