布隆過濾器的原理與實現

1、基本概念

布隆過濾器(Bloom Filter)是1970年由布隆提出的。它其實是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器能夠用於檢索一個元素是否在一個集合中。
它的優勢是空間效率和查詢時間都遠遠超過通常的算法,缺點是有必定的誤識別率和刪除困難。
Google爬蟲它要判斷。哪些網頁是被爬過來了的。
若是想要判斷一個元素是否是在一個集合裏,通常想到的是將全部元素保存起來,而後經過比較肯定。鏈表,樹等等數據結構都是這種思路,可是隨着集合中元素的增長,咱們須要的存儲空間愈來愈大,檢索速度也愈來愈慢(O(n),O(logn))。
不過世界上還有一種叫做散列表(又叫哈希表,Hash table)的數據結構(有一個動態數組+ 一個hash函數)。它能夠經過一個Hash函數將一個元素映射成一個位陣列(Bit array)中的一個點。這樣一來,咱們只要看看這個點是否是1就能夠知道集合中有沒有它了。這就是布隆過濾器的基本思想。
Hash面臨的問題就是衝突。假設Hash函數是良好的,若是咱們的位陣列長度爲m個點,那麼若是咱們想將衝突率下降到例如 1%, 這個散列表就只能容納m / 100個元素。顯然這就不叫空間效率了(Space-efficient)了。解決方法也簡單,就是使用多個Hash,若是它們有一個說元素不在集合中,那確定就不在。若是它們都說在,雖然也有必定可能性它們在說謊,不過直覺上判斷這種事情的機率是比較低的。
Bloom Filter是一種空間效率很高的隨機數據結構,它利用位數組很簡潔地表示一個集合,並能判斷一個元素是否屬於這個集合。Bloom Filter的這種高效是有必定代價的:在判斷一個元素是否屬於某個集合時,有可能會把不屬於這個集合的元素誤認爲屬於這個集合(false positive)。所以,Bloom Filter不適合那些「零錯誤」的應用場合。而在能容忍低錯誤率的應用場合下,Bloom Filter經過極少的錯誤換取了存儲空間的極大節省。
總結起來講:
bloomfilter,布隆過濾器:迅速判斷一個元素是否是在一個龐大的集合內,可是他有一個
弱點:它有必定的誤判率誤判率:本來不存在於該集合的元素,布隆過濾器有可能會判斷說它存在,可是,若是布隆過濾器判斷說某一個元素不存在該集合,那麼該元素就必定不在該集合內。








python

2、布隆過濾器的優缺點

一、優勢

相比於其它的數據結構,布隆過濾器在空間和時間方面都有巨大的優點。
(1)布隆過濾器存儲空間和插入/查詢時間都是常數。
(2)另外, Hash函數相互之間沒有關係,方便由硬件並行實現。
(3)布隆過濾器不須要存儲元素自己,在某些對保密要求很是嚴格的場合有優點。
(4)布隆過濾器能夠表示全集,其它任何數據結構都不能;
(5)k和m相同,使用同一組Hash函數的兩個布隆過濾器的交併差運算可使用位操做行。
(6)能快速的判斷元素存在不存在,遠遠的縮小存儲數據的規模。





算法

二、缺點

可是布隆過濾器的缺點和優勢同樣明顯。
(1)誤算率是其中之一。隨着存入的元素數量增長,誤算率隨之增長。可是若是元素數量太少,則使用散列表足矣。
(2)另外,通常狀況下不能從布隆過濾器中刪除元素。咱們很容易想到把位列陣變成整數數組,每插入一個元素相應的計數器加1, 這樣刪除元素時將計數器減掉就能夠了。然而要保證安全的刪除元素並不是如此簡單。首先咱們必須保證刪除的元素的確在布隆過濾器裏面. 這一點單憑這個過濾器是沒法保證的。
(3)另外計數器迴繞也會形成問題。在下降誤算率方面,有很多工做,使得出現了不少布隆過濾器的變種。


數據庫

三、使用場景考量

(1)存在必定的誤判率,那麼在你不能容忍有錯誤率的狀況,布隆過濾器不適用;
(2)布隆過濾器不支持刪除操做
編程

3、實現原理

布隆過濾器須要的是一個位數組(跟位圖(bitmap)相似, bytes數組)和K個映射函數(跟Hash表相似),在初始狀態時,對於長度爲m的位數組array,它的全部位被置0。
布隆過濾器的原理與實現
數組

一、布隆過濾器添加元素

(1)對於有n個元素的集合S={S1,S2...Sn},經過k個映射函數{f1,f2,......fk};
(2)將集合S中的每一個元素Sj(1<=j<=n)映射爲k個值{g1,g2...gk},
(3)而後再將位數組array中相對應的array[g1],array[g2]......array[gk]置爲1。

安全

二、布隆過濾器查詢元素

(1)查詢W元素是否存在集合中的時候,將W經過哈希映射函數{f1,f2,......fk},獲得集合g
(2)獲得集合g的K個值{g1,g2...gk},對應位數組上的k個點。
(3)若是k個點的其中有一個點不爲1,則能夠判斷該元素必定不存在集合中。反之,若是k個點都爲1,則該元素可能存在集合中。
注意:此處不能判斷該元素是否必定存在集合中,可能存在必定的誤判率。能夠從圖中能夠看到:假設某個元素經過映射對應下標爲4,5,6這3個點。雖然這3個點都爲1,可是很明顯這3個點是不一樣元素通過哈希獲得的位置,所以這種狀況說明元素雖然不在集合中,也可能對應的都是1,這是誤判率存在的緣由。


網絡

三、自定義一個布隆過濾器的時候須要作的事情

(1)初始化一個位數組
(2)實現K個hash函數
(3)實現查詢和插入操做
查詢和插入操做須要作的事情:對插入進來的值進行hash計算,有幾個hash函數,就計算幾回,每次計算出來的結果值,都根據這個值,去位數組裏面把相應位置的0變成1;
對查詢操做來講,只須要把你要查詢的這個key值進行k個hash函數的調用,而後再判斷計算出來的這個k個值對應的維數組上的值是否是有一個爲0,若是有一個爲0,那就表示,該key不在這個集合裏面。



數據結構

4、哈希函數/哈希表

一、概念

哈希表中元素是由哈希函數肯定的。將數據元素的關鍵字K做爲自變量,經過必定的函數關係(稱爲哈希函數),計算出的值,即爲該元素的存儲地址,也即一個元素在哈希表中的位置是由哈希函數決定的。dom

二、特色

(1)若是兩個散列值是不相同的(根據同一函數),那麼這兩個散列值的原始輸入也是不相同的。
(2)散列函數的輸入和輸出不是惟一對應關係的,若是兩個散列值相同,兩個輸入值極可能是相同的。但也可能不一樣,這種狀況稱爲 「散列碰撞」(或者 「散列衝突」)。
ide

三、哈希構造方法

(1)直接定址法
取關鍵字或關鍵字的某個線性函數值爲哈希地址。即H(key)=key 或 H(key)=akey+b (a,b爲常數)。
(2)數字分析法
若關鍵字是以r爲基的數(如:以10爲基的十進制數),而且哈希表中可能出現的關鍵字都是事先知道的,則可取關鍵字的若干數位組成哈希地址。
(3)平方取中法
取關鍵字平方後的中間幾位爲哈希地址,是比較經常使用的一種。
(4)摺疊法
將關鍵字分割成位數相同的幾部分(最後一部分的位數可不一樣),而後取這幾部分的疊加和(捨去進位)做爲哈希地址。適用於關鍵字位數比較多,且關鍵字中每一位上數字分佈大體均勻時。
(5)除留餘數法
取關鍵字被某個不大於哈希表表長m的數p除後所得餘數爲哈希地址(p爲素數)
H(key)=key MOD p,p<=m (最簡單,最經常使用)p的選取很重要
通常狀況,p能夠選取爲質數或者不包含小於20的質因數的合數(合數指天然數中除了能被1和自己整除外,還能被其餘數(0除外)整除的數)。










(6)隨機數法
選擇一個隨機函數,取關鍵字的隨機函數值爲它的哈希地址。即H(key)=rando(key),其中random爲隨機函數。適用於關鍵字長度不等時。

總結:實際工做中根據狀況不一樣選用的哈希函數不一樣,一般,考慮因素以下:

(1)計算哈希函數所需時間(包括硬件指令的因素)

(2)關鍵字的長度

(3)哈希表的大小

(4)關鍵字的分佈狀況

(5)記錄的查找頻率

四、哈希碰撞

概念:即兩個不一樣的關鍵字,經過同一個哈希函數計算得出的結果值同樣的。

五、解決哈希碰撞

(1)拉鍊法
拉出一個動態鏈表代替靜態順序存儲結構,能夠避免哈希函數的衝突,不過缺點就是鏈表的設計過於麻煩,增長了編程複雜度。此法能夠徹底避免哈希函數的衝突。
(2)多哈希法
設計二種甚至多種哈希函數,能夠避免衝突,可是衝突概率仍是有的,函數設計的越好或越多均可以將概率降到最低(除非人品太差,不然幾乎不可能衝突)。
(3)開放地址法
開放地址法有一個公式:Hi=(H(key)+di) MOD m i=1,2,...,k(k<=m-1)
其中,m爲哈希表的表長。di 是產生衝突的時候的增量序列。
若是di值可能爲1,2,3,...m-1,稱線性探測再散列。
若是di取1,則每次衝突以後,向後移動1個位置。
若是di取值可能爲1,-1,4,-4,9,-9,16,-16,...kk,-kk(k<=m/2)稱二次探測再散列。
若是di取值可能爲僞隨機數列,稱僞隨機探測再散列。
(4)建域法
假設哈希函數的值域爲[0,m-1],則設向量HashTable[0..m-1]爲基本表,另外設立存儲空間向量OverTable[0..v]用以存儲發生衝突的記錄。











5、誤判率估計

如今咱們瞭解了布隆過濾器的大體工做原理了,那咱們就來計算一下這個誤判率。

數組的大小:m 
總共的數據大小爲:n 
hash函數的個數爲:k

假設布隆過濾器中的hash function(哈希函數)知足simple uniform hashing(單一均勻散列)假設:每一個元素都等機率地hash到m個slot中的任何一個,與其它元素被hash到哪一個slot無關。若m爲bit數,則:
對某一特定bit位,在一個元素調用了某個hash函數以後,被改爲了1的機率是:
布隆過濾器的原理與實現
對某一特定bit位,在一個元素由某特定hash function插入時沒有被置位爲1的機率爲:
布隆過濾器的原理與實現
則k個hash function中沒有一個對其置位爲1的機率,也就是該bit位在 k 次hash以後還一直保持爲0的機率:
布隆過濾器的原理與實現





若是插入了n個元素,但都未將其置爲1,也就是當全部的元素都被插入進來之後,某一個特定的bit位尚未被改爲1的機率:
布隆過濾器的原理與實現
則此位置被置爲1(被改爲了1)的機率,也就是當全部的元素都被插入進來之後,某一個特定的bit位被改爲1的機率:
布隆過濾器的原理與實現


如今檢測某一元素是否在該集合中。代表某個元素是否在集合中所需的 k 個位置都按照如上的方法設置爲 "1",可是該方法可能會使算法錯誤的認爲某一本來不在集合中的元素卻被檢測爲在該集合中(False Positives),即k個位置都是1的機率如下公式肯定:

布隆過濾器的原理與實現

其實上述結果是在假定由每一個 Hash 計算出須要設置的位(bit) 的位置是相互獨立爲前提計算出來的,不難看出,隨着 m(位數組大小)的增長,假正例(False Positives)的機率會降低,同時隨着插入元素個數 n 的增長,False Positives的機率又會上升,對於給定的m,n。
(1)如何選擇Hash函數個數 k 由如下公式肯定:

布隆過濾器的原理與實現
推導過程:
由上面計算出的結果,如今計算對於給定的m和n,k爲什麼值時可使得誤判率最低。設誤判率爲k的函數爲:
布隆過濾器的原理與實現
翻譯一下,也就是當m和n肯定了之後,咱們應該設置k爲多少能使誤判率最低呢?
當肯定了m和n以後,咱們要求出一個k使f(k)的值最小。
咱們能夠肯定k,m,n三者之間的關係以後,咱們能夠保證誤判率最小
首先,設
布隆過濾器的原理與實現
則上面的式子化簡爲:
布隆過濾器的原理與實現
對兩邊都取對數,得出:
布隆過濾器的原理與實現
兩邊對k求導,得出:
布隆過濾器的原理與實現













接着,來求最值:
布隆過濾器的原理與實現

因此:
布隆過濾器的原理與實現

因此:
布隆過濾器的原理與實現
因此:
布隆過濾器的原理與實現


此時的誤判率:
布隆過濾器的原理與實現
能夠看出若要使得誤判率≤1/2,則:
布隆過濾器的原理與實現


(2)而對於給定的False Positives機率 p,選擇最優的位數組大小m的公式爲:
布隆過濾器的原理與實現

上式代表,位數組的大小最好與插入元素的個數成線性關係,對於給定的 m,n,k,假正例機率最大爲:

布隆過濾器的原理與實現

六、代碼實現

(1)python代碼實現

import mmh3
from bitarray import bitarray

# zhihu_crawler.bloom_filter

# Implement a simple bloom filter with murmurhash algorithm.
# Bloom filter is used to check wether an element exists in a collection, and it has a good performance in big data situation.
# It may has positive rate depend on hash functions and elements count.

BIT_SIZE = 5000000

class BloomFilter:

    def init(self):
        # Initialize bloom filter, set size and all bits to 0
        bit_array = bitarray(BIT_SIZE)
        bit_array.setall(0)

        self.bit_array = bit_array

    def add(self, url):
        # Add a url, and set points in bitarray to 1 (Points count is equal to hash funcs count.)
        # Here use 7 hash functions.
        point_list = self.get_postions(url)

        for b in point_list:
            self.bit_array[b] = 1

    def contains(self, url):
        # Check if a url is in a collection
        point_list = self.get_postions(url)

        result = True
        for b in point_list:
            result = result and self.bit_array[b]

        return result

    def get_postions(self, url):
        # Get points positions in bit vector.
        point1 = mmh3.hash(url, 41) % BIT_SIZE
        point2 = mmh3.hash(url, 42) % BIT_SIZE
        point3 = mmh3.hash(url, 43) % BIT_SIZE
        point4 = mmh3.hash(url, 44) % BIT_SIZE
        point5 = mmh3.hash(url, 45) % BIT_SIZE
        point6 = mmh3.hash(url, 46) % BIT_SIZE
        point7 = mmh3.hash(url, 47) % BIT_SIZE

        return [point1, point2, point3, point4, point5, point6, point7]```

# 七、總結
在計算機科學中,咱們經常會碰到時間換空間或者空間換時間的狀況,即爲了達到某一個方面的最優而犧牲另外一個方面。Bloom Filter在時間空間這兩個因素以外又引入了另外一個因素:錯誤率。在使用Bloom Filter判斷一個元素是否屬於某個集合時,會有必定的錯誤率。也就是說,有可能把不屬於這個集合的元素誤認爲屬於這個集合(False Positive),但不會把屬於這個集合的元素誤認爲不屬於這個集合(False Negative)。在增長了錯誤率這個因素以後,Bloom Filter經過容許少許的錯誤來節省大量的存儲空間。
自從Burton Bloom在70年代提出Bloom Filter以後,Bloom Filter就被普遍用於拼寫檢查和數據庫系統中。近一二十年,伴隨着網絡的普及和發展,Bloom Filter在網絡領域得到了新生,各類Bloom Filter變種和新的應用不斷出現。能夠預見,隨着網絡應用的不斷深刻,新的變種和應用將會繼續出現,BloomFilter必將得到更大的發展。
相關文章
相關標籤/搜索