在大數據與雲計算髮展的時代,咱們常常會碰到這樣的問題。咱們是否能高效的判斷一個用戶是否訪問過某網站的主頁(天天訪問量上億)或者須要統計網站的pv、uv。最直接的想法是將全部的訪問者存起來,而後每次用戶訪問的時候與以前集合進行比較。不論是將訪問信息存在內存(或數據庫)都會對服務器形成很是大的壓力。那是否存在一種方式,容忍必定的錯誤率,高效(計算複雜度、空間複雜度)的實現訪問量信息的跟蹤、統計呢?接下來介紹的布隆過濾器(BloomFilter)就能夠知足當前的使用場景(註釋:基數計數法一樣能知足pv、uv的統計)。html
布隆過濾器(BloomFilter)是1970年由布隆提出的一種空間空間效率很高的隨機數據結構,它利用位數組很簡潔地表示一個集合,並判斷一個元素是否屬於這個集合。使用布隆過濾器,存在第一類出錯(Falsepositive),可是不會存在第二類錯誤(Falsenegative),所以,布隆過濾器擁有100%的召回率。也就是說,布隆過濾器可以準確判斷一個元素不在集合內,但只能判斷一個元素可能在集合內。所以,BloomFilter不適合「零錯誤」的應用場合。在可以容忍低錯誤的應用場合下,BloomFilter經過極少的錯誤換取了存儲空間的極大節省。咱們能夠向布隆過濾器裏添加元素,可是不能從中移除元素(普通布隆過濾器,加強的布隆過濾器是能夠移除元素的)。隨着布隆過濾器中元素的增長,犯第一類錯誤的可能性也隨之增大。ios
一個空的布隆過濾器有長度爲M比特的bit數組構成,且全部位都初始化0。一個元素經過K個不一樣的hash函數隨機散列到bit數組的K個位置上,K必須遠小於M。K和M的大小由錯誤率(falsepositiverate)決定。算法
Bloom Filter的一個例子集合S{x,y,z}。帶有顏色的箭頭表示元素通過k(k=3)hash函數的到在M(bit數組)中的位置。元素W不在S集合中,由於元素W通過k個hash函數獲得在M(bit數組)的k個位置中存在值爲0的位置。數據庫
向集合S中添加元素x:x通過k個散列函數後,在M中獲得k個位置,而後,將這k個位置的值設置爲1。apache
判斷x元素是否在集合S中:x通過k個散列函數後,的到k個位置的值,若是這k個值中間存在爲0的,說明元素x不在集合中——元素x曾經插入到過集合S,則M中的k個位置會所有置爲1;若是M中的k個位置全爲1,則有兩種情形。情形一:這個元素在這個集合中;情形二:曾經有元素插入的時候將這k個位置的值置爲1了(第一類錯誤產生的緣由FalsePositive)。簡單的布隆過濾器沒法區分這兩種狀況,在加強版中解決了這個問題。數組
設計k個相互獨立的hash函數可能工做量比較大,可是一個好的hash函數是下降誤判率的關鍵。一個良好的hash函數應該有寬輸出,他們之間的衝突應儘可能低,這樣k個hash函數能靜可能的將值hash的更多的位置。hash函數的設計是咱們能夠將k個不一樣的值( 0, 1, ..., k − 1)做爲參數傳入,或者將它們加入主鍵中。對於大的M或者k,hash函數之間的獨立性對誤判率影響很是大((Dillinger & Manolios (2004a),Kirsch & Mitzenmacher (2006))),Dillinger在k個散列函數中,屢次使用同一個函數散列,分析對誤判率的影響。服務器
對於簡單布隆過濾器來講,從集合S中移除元素x是不可能的,且falsenegatives不容許。元素散列到k個位置,儘管能夠 將這k個位置的值置爲0來移除這個元素,可是這同事也移除了那些散落後,有值落在這k位中的元素。所以,沒有一種方法能夠判斷移除這個元素後是否影響其它已經加入集合中的元素,將k個位置置爲0會引入二類偏差(falsenegative)。網絡
在falsepositives的狀況下,布隆過濾器相比其它的集合(平衡二叉樹、樹、hash表、數組、鏈表)只須要少許的存儲空間。布隆過濾器的添加和檢查元素是否在集合內的複雜度爲O(K)。Hash表的平均複雜度比布隆過濾器更低。Bloom過濾器在偏差最優的狀況下,平均每一個元素大概是1.44bit。數據結構
布隆過濾器判斷一個元素是否屬於它表示的集合時會存在已定的錯誤率(falsepositiverate),接下來就估計錯誤率的大小。在估計偏差前,咱們假設kn<m(k哈希函數的個數,n集合中元素的個數,mbit數組的長度)且哈希函數之間時相互獨立的,哈希函數散列的bit數組M中的位置時徹底隨機的。函數
一個長度爲m的bit數組,元素在插入時通過一次哈希散列後bit數組的某個位置的值沒有被置爲1的機率爲
通過k個哈希函數散列後,還未被置爲1的機率爲
若是插入n個元素後,該位置還未被置爲1的機率爲
因此被置爲1的機率爲
如今判斷一個元素是否在結合中,通過k個函數散列到k個bit數組的不一樣位置。全部這些位置的值爲1的機率——誤判率。
這裏使用了極限
這種計算方法不嚴格,由於前面假設哈希函數和散列後值的分佈是相互獨立的。可是,這個假設隨着m和n的增大誤判率更接近真實的誤判率。
Mitzenmacher and Upfal證實無假設狀況下的誤判率的指望值相同。
既然布隆過濾器將集合映射到位數組中,那麼選多少個hash函數纔是錯誤率最低的狀況。這裏有兩個互斥的理由:若是哈希函數的個數多,那麼在對一個不屬於集合的元素進行查詢時獲得0的機率就大;但另外一方面,若是哈希函數的個數少,那麼位數組中的0就多。爲了獲得最優的哈希函數個數,咱們須要根據上一小節中的錯誤率公式進行計算。
誤判率
兩邊取天然對數,
,只要g取最小值,p就能取到最小值。因爲p=e^(-nk/m),咱們能夠將g改寫爲
根據對稱法則能夠獲得當p=1/2時,也就是k=ln2*(m/n)時,g取得最小值,在這種狀況下,最小的錯誤率p=(1/2)^k≈(0.6185)^(m/n)。p=1/2對應着位數組中0和1各半。換句話說,想保持錯誤率低,最好讓位數組有一半還空着。
在給定n(集合中元素的個數)和錯誤率(最優函數個數k的的錯誤率)的狀況下,位數組M的大小計算,在最優k的狀況下
化簡爲
獲得
這意味着在錯誤率爲p的狀況下,布隆過濾器的長度爲m才能容納n個元素(以上計算基於n,m->∞)。
Swamidass & Baldi (2007)給出了布隆過濾器元素個數估算的方法(詳細證實方式參考論文)
其中,n*表示布隆過濾元素個數的估算值,m表示布隆過濾器的大小,k表示哈希函數的個數,X表示布隆過濾器位值位1的個數。
布隆過濾器能夠用來估算兩個集合之間的併合交。一下給出兩個集合之間並的計算方式:
A和B之間的並集的個數爲:
因此A*與B*之間的交集的個數爲:
布隆過濾器可以容納任意多的元素(誤判率會增長),老是能向布隆過濾器中添加元素,不會報錯(OutMemory等);
布隆過濾器能夠很方便的經過計算機的or \and操做計算兩個集合元素之間的交集(intersection)和並集(union),可是一樣影響布隆過濾的準確性。
Googlebigtable、apachehbase和apachecassandra使用bloom過濾器判斷是否存在該行(rows)或(colums),以減小對磁盤的訪問,提升數據庫的訪問性能;
比特幣使用布隆過濾判斷錢包是否同步OK。
在計算機這個領域裏,咱們經常碰到時間換空間\或空間換時間的狀況,爲了達到某一方面的性能,犧牲另一方面。BloomFilter在時間和空間着二者之間引入了另一個概念——錯誤率。也就是前文提到的布隆過濾不能準確判斷一個元素是否在集合內(相似的設計還有基數統計法)。引入錯誤率後,極大的節省了存儲空間。
自從Burton Bloom在70年代提出Bloom Filter以後,Bloom Filter就被普遍用於拼寫檢查和數據庫系統中。近一二十年,伴隨着網絡的普及和發展,Bloom Filter在網絡領域得到了新生,各類Bloom Filter變種和新的應用不斷出現。能夠預見,隨着網絡應用的不斷深刻,新的變種和應用將會繼續出現,Bloom Filter必將得到更大的發展。