看見了海量數據去重,找到停留時間最長的IP等問題,有博友提到了Bloom Filter,我就查了查,不過首先想到的是大叔,下面就先看看大叔的風采。java
1、布隆過濾器概念引入算法
(Bloom Filter)是由布隆(Burton Howard Bloom)在1970年提出的。它其實是由一個很長的二進制向量和一系列隨機映射函數組成,布隆過濾器能夠用於檢索一個元素是否在一個集合中。它的優勢是空間效率和查詢時間都遠遠超過通常的算法,缺點是有必定的誤識別率(假正例False positives,即Bloom Filter報告某一元素存在於某集合中,可是實際上該元素並不在集合中)和刪除困難,可是沒有識別錯誤的情形(即假反例False negatives,若是某個元素確實在該集合中,那麼Bloom Filter 是不會報告該元素不存在於集合中的,因此不會漏報)。數據庫
下面從簡單的排序談到BitMap算法,再談到數據去重問題,談到大數據量處理利器:布隆過濾器。網頁爬蟲
給定數據(2,4,1,12,9,7,6)如何對它排序?數組
方法1:基本的排序方法包括冒泡,快排等。函數
方法2:使用BitMap算法大數據
方法1就不介紹了,方法2中所謂的BitMap是一個位數組,跟平時使用的數組的惟一差異在於操做的是位。首先是開闢2個字節大小的位數組,長度爲12(該長度由上述數據中最大的數字12決定的),而後,讀取數據,2存放在位數組中下標爲1的地方,值從0改成1,4存放在下標爲3的地方,值從0改成1....最後,讀取該位數組,獲得排好序的數據是:(1,2,4,6,7,9,12)。this
比較方法1和方法2的差異:方法2中,排序須要的時間複雜度和空間複雜度很依賴與數據中最大的數字好比12,所以空間上講須要開2個字節大小的內存,時間上須要遍歷完整個數組。當數據相似(1,1000,10萬)只有3個數據的時候,顯然用方法2,時間複雜度和空間複雜度至關大,可是當數據比較密集時該方法就會顯示出來優點。url
數據(2,4,1,12,2,9,7,6,1,4)如何找出重複出現的數字?spa
首先是開闢2個字節大小的位數組,長度爲12(該長度由上述數據中最大的數字12決定的,當讀取完12後,當讀取2的時候,發現數組中的值是1,則判斷出2是重複出現的。
2、布隆過濾器原理
布隆過濾器須要的是一個位數組(這個和位圖有點相似)和k個映射函數(和Hash表相似),在初始狀態時,對於長度爲m的位數組array,它的全部位都被置爲0。對於有n個元素的集合S={s1,s2......sn},經過k個映射函數{f1,f2,......fk},將集合S中的每一個元素sj(1<=j<=n)映射爲k個值{g1,g2......gk},而後再將位數組array中相對應的array[g1],array[g2]......array[gk]置爲1;若是要查找某個元素item是否在S中,則經過映射函數{f1,f2.....fk}獲得k個值{g1,g2.....gk},而後再判斷array[g1],array[g2]......array[gk]是否都爲1,若全爲1,則item在S中,不然item不在S中。這個就是布隆過濾器的實現原理。
固然有讀者可能會問:即便array[g1],array[g2]......array[gk]都爲1,能表明item必定在集合S中嗎?不必定,由於有這個可能:就是集合中的若干個元素經過映射以後獲得的數值恰巧包括g1,g2,.....gk,那麼這種狀況下可能會形成誤判,可是這個機率很小,通常在萬分之一如下。
很顯然,布隆過濾器的誤判率和這k個映射函數的設計有關,到目前爲止,有不少人設計出了不少高效實用的hash函數。尤爲要注意的是,布隆過濾器是不容許刪除元素的(實際就是由於多個str可能都應設在同一點,而判斷str存在的話是全部映射點都存在,因此不能刪除),由於若刪除一個元素,可能會發生漏判的狀況。不過有一種布隆過濾器的變體Counter Bloom Filter,能夠支持刪除元素,感興趣的讀者能夠查閱相關文獻資料。
3、布隆過濾器False positives 機率推導
假設 Hash 函數以等機率條件選擇並設置 Bit Array 中的某一位,m 是該位數組的大小,k 是 Hash 函數的個數,那麼位數組中某一特定的位在進行元素插入時的 Hash 操做中沒有被置位爲1的機率是:;那麼在全部 k 次 Hash 操做後該位都沒有被置 "1" 的機率是:;若是咱們插入了 n 個元素,那麼某一位仍然爲 "0" 的機率是:於是該位爲 "1"的機率是:;如今檢測某一元素是否在該集合中。標明某個元素是否在集合中所需的 k 個位置都按照如上的方法設置爲 "1",可是該方法可能會使算法錯誤的認爲某一本來不在集合中的元素卻被檢測爲在該集合中(False Positives),該機率由如下公式肯定:。
其實上述結果是在假定由每一個 Hash 計算出須要設置的位(bit) 的位置是相互獨立爲前提計算出來的,不難看出,隨着 m (位數組大小)的增長,假正例(False Positives)的機率會降低,同時隨着插入元素個數 n 的增長,False Positives的機率又會上升,對於給定的m,n,如何選擇Hash函數個數 k 由如下公式肯定:;此時False Positives的機率爲:;而對於給定的False Positives機率 p,如何選擇最優的位數組大小 m 呢,;該式代表,位數組的大小最好與插入元素的個數成線性關係,對於給定的 m,n,k,假正例機率最大爲:。
4、布隆過濾器應用
布隆過濾器在不少場合能發揮很好的效果,好比:網頁URL的去重,垃圾郵件的判別,集合重複元素的判別,查詢加速(好比基於key-value的存儲系統)等,下面舉幾個例子:
很顯然,直接利用Hash表會超出內存限制的範圍。這裏給出兩種思路:
第一種:若是不容許必定的錯誤率的話,只有用分治的思想去解決,將A,B兩個集合中的URL分別存到若干個文件中{f1,f2...fk}和{g1,g2....gk}中,而後取f1和g1的內容讀入內存,將f1的內容存儲到hash_map當中,而後再取g1中的url,如有相同的url,則寫入到文件中,而後直到g1的內容讀取完畢,再取g2...gk。而後再取f2的內容讀入內存。。。依次類推,知道找出全部的重複url。
第二種:若是容許必定錯誤率的話,則能夠用布隆過濾器的思想。
量很多時,在判重時會形成效率低下,此時常見的一種作法就是利用布隆過濾器,還有一種方法是利用berkeley db來存儲url,Berkeley db是一種基於key-value存儲的非關係數據庫引擎,可以大大提升url判重的效率。
布隆過濾器主要運用在過濾惡意網址用的,將全部的惡意網址創建在一個布隆過濾器上,而後對用戶的訪問的網址進行檢測,若是在惡意網址中那麼就通知用戶。這樣的話,咱們還能夠對一些常出現判斷錯誤的網址設定一個白名單,而後對出現判斷存在的網址再和白名單中的網址進行匹配,若是在白名單中,那麼就放行。固然這個白名單不能太大,也不會太大,布隆過濾器錯誤的機率是很小的。
5、布隆過濾器簡單Java實現
package a; import java.util.BitSet; /* * 存在的問題 * DEFAULT_LEN長度設置爲多少合適呢? * 我發現result和DEFAULT_LEN有關,不該該啊,沒發現緣由 */ public class BloomFilterTest { //30位,表示2^2^30種字符 static int DEFAULT_LEN = 1<<30; //要用質數 static int[] seeds = {3,5,7,11,17,31}; static BitSet bitset = new BitSet(DEFAULT_LEN); static MyHash[] myselfHash = new MyHash[seeds.length]; public static void main(String[] args) { String str = "791909235@qq.com"; //生成一次就夠了 for(int i=0; i<seeds.length; i++) { myselfHash[i] = new MyHash(DEFAULT_LEN, seeds[i]); } bitset.clear(); for(int i=0; i<myselfHash.length; i++) { bitset.set(myselfHash[i].myHash(str),true); } boolean flag = containsStr(str); //System.out.println("========================"); System.out.println(flag); } private static boolean containsStr(String str) { // TODO Auto-generated method stub if(null==str) return false; for(int i=0; i<seeds.length; i++) { if(bitset.get(myselfHash[i].myHash(str))==false) return false; } return true; } } class MyHash { int len; int seed; public MyHash(int len, int seed) { super(); this.len = len; this.seed = seed; } public int myHash(String str) { int len = str.length(); int result = 0; //這的len就是str的len,不是成員變量的len for(int i=0; i<len; i++) { //System.out.println(seed+"oooooooooooo"); result = result*seed + str.charAt(i); //System.out.println(result); //長度就是1<<24,若是大於這個數 感受結果不許確 //<0就是大於了0x7ffffff if(result>(1<<30) || result<0) { //System.out.println("-----"+(1<<30)); System.out.println(result+"myHash數據越界!!!"); break; } } return (len-1)&result; } }