特殊的數據結構(布隆過濾器)的原理和實現及探究

1、布隆過濾器的使用價值

有時候咱們須要判斷一個元素是否在一個集合中。好比,在字處理軟件中,須要檢查一個單詞是否拼寫正確(也就是要判斷它是否在已知的字典裏);在警察系統中,一個嫌疑人的名字是否出如今嫌疑名單上;在網絡爬蟲裏,一個網址是否已經被訪問過,等等。php

最直接的方法就是講集合中的元素存在計算機中,遇到一個新元素時,將它和集合中的元素直接比較便可。通常來說,計算機中的集合是用哈希表(Hash Table)來存儲的。它的好處是快速準確,缺點是耗費存儲空間。css

爲何說耗費存儲空間呢?其根本緣由是哈希表方法須要把實實在在的具備特定長度(每一個Email地址對應成一個8字節的信息指紋)的元素的信息指紋存儲在內存或硬盤中的哈希表中,這個存儲量在實際應用中通常是至關大的。好比每存儲一億個Email地址,須要0.8G大小的數字指紋存儲空間,考慮到哈希表的存儲空間利用率通常只有一半,因此須要1.6G的存儲空間。若是存儲幾十億上百億的Email地址,那就須要百億字節的內存存儲空間。html

而布隆過濾器只須要哈希表1/8到1/4的大小就能解決一樣的問題,它其實是一個很長的二進制向量和一系列的隨機映射函數。git

下面以WEB頁面地址的存儲爲例來講明布隆過濾器的工做原理。web

假定存儲一億個WEB頁面地址,先創建一個16億二進制(比特),即2億字節的向量,而後將這16億個二進制位清零。對於每個WEB頁面地址X,用8個隨機數產生器(f1,f2,...,f8)。再用一個隨機數產生器G把這8個信息指紋映射到1-16億中的8個天然數g1,g2,...g8。如今把這8個位置的二進制位都置爲1。對着一億個WEB頁面地址都進行這樣的處理後,一個針對WEB頁面的布隆過濾器就建成了,見下圖。api

布隆迪過濾器的映射方法網絡

如今,讓咱們看看如何用布隆過濾器來檢測一個WEB網頁地址Y是否已經被咱們收錄。用相同的8個隨機數生成器(f1,f2,...,f8)對這個WEB網頁地址產生8個信息指紋s1,s2,...s8,而後將這8個指紋對應到布隆過濾器的8個二進制位,分別是t1,t2,...,t8。若是Y已被收錄,顯然t1,t2,...,t8對應的8個二進制位必定是1。經過這樣的方式咱們可以很快地肯定一個WEB頁面是否已被咱們收錄。app

2、布隆過濾器的實現

布隆過濾器實現代碼:函數

#encoding=UTF-8 
'''
Created on 2014年6月21日
@author: jin
'''
import BitVector  
class MyHash():#哈希類,根據不一樣參數初始化後做爲不一樣的哈希函數 
      
    def __init__(self, cap, seed):  
        self.cap = cap  
        self.seed = seed  
      
    def hash(self, value): #計算哈希值得過程 
        ret = 0  
        for i in range(len(value)):  
            ret += self.seed*ret + ord(value[i]) #ord()函數計算傳入的url字符串中每個字符在ASCII碼錶中對應的順序值
        return (self.cap-1) & ret  #返回哈希值,即在比特序列中的位置    
 
class BloomFilter(): 
        
    def __init__(self, BIT_SIZE=1<<31):  
        self.BIT_SIZE = 1 << 31  #不攏過濾器的比特數,
        self.seeds = [5, 7, 11, 13,19, 31, 37, 61]  #8個種子,用於產生hash函數
        self.bitset = BitVector.BitVector(size=self.BIT_SIZE)  
        self.hashFuncList = []           
        for i in range(len(self.seeds)):  
            self.hashFuncList.append(MyHash(self.BIT_SIZE, self.seeds[i]))  #對每一個種子,建立一個MyHash對象,一共8個          
    def insert(self, value):  #插入值,這裏並不是真正地插入並存儲,而是把該值對應的8個位置的比特位置爲1
        for function in self.hashFuncList:  
            locationBit = function.hash(value)  #計算應該置爲1的比特位
            self.bitset[locationBit] = 1  
    def isContaions(self, value):  
        if value == None:  
            return False  
        ret = True  
        for f in self.hashFuncList:  
            locationBit = f.hash(value)  
            ret = ret & self.bitset[locationBit]  #能夠看出,對8個哈希函數,只要有一個爲0,那麼將返回0,即該值還沒有存在
        return ret 
       
def Main(): #主函數
 
    fd = open("urls.txt")  #有重複的網址 http://www.kalsey.com/tools/buttonmaker/
    bloomfilter = BloomFilter()  
    while True:  
        url = fd.readline()  
        if cmp(url, 'exit') == 0:
            print 'complete and exit now'
            break  
        elif bloomfilter.isContaions(url) == False:  
            bloomfilter.insert(url)  
        else:  
            print 'url :%s has exist' % url                 
Main()

url.txt內部存儲有一系列網址,最後一行是‘exit’,內容以下:google

http://sourceforge.net/robots.txt
http://sourceforge.net/
http://sourceforge.net
http://sourceforge.net and https://sourceforge.net
http://sourceforge.net/sitemap.xml
http://sourceforge.net/allura_sitemap/sitemap.xml
http://sourceforge.net/directory_sitemap.xml
http://a.fsdn.com
http://a.fsdn.com/con/img/sftheme/favicon.ico
http://a.fsdn.com/con/js/min/sf.head.js
http://a.fsdn.com/con/js/sftheme/dd_belatedpng.js
http://fonts.googleapis.com
http://fonts.googleapis.com/css
http://a.fsdn.com/con/css/sf.css
http://sourceforge.net/blog/feed/
http://email.playtime.uni.cc/ 
http://services.nexodyne.com/email/ 
http://gizmo967.mgs3.org/Gmail/ 
http://www.hkwebs.net/catalog/tools/gmail/ 
http://sagittarius.dip.jp/~toshi/cgi-bin/designmail/designmail.html 
http://www.eoool.com/
http://sourceforge.netand
https://sourceforge.net
http://a.fsdn.com/con/js/adframe.js
http://sourceforge.net/directory/
http://kalsey.com/tools/buttonmaker/ 
http://www.lucazappa.com/brilliantMaker/buttonImage.php 
http://www.feedforall.com/public/rss-graphic-tool.htm  
http://www.yugatech.com/make.php 
http://www.hkwebs.net/catalog/tools/buttonmaker/index.php
http://phorum.com.tw/Generator.aspx 
http://www.logoyes.com/lc_leftframe.htm 
http://cooltext.com/Default.aspx
exit

運行效果以下,能夠看到未發生存儲地址衝突:

complete and exit now

往url.txt裏面再增長一個原來未有的網址:

http://www.kalsey.com/tools/buttonmaker/

再次運行,居然發生了衝突,以下:

url :http://www.kalsey.com/tools/buttonmaker/
 has exist
complete and exit now

這說明此網址和另一個網址對應的8個信息指紋相同,雖然它們自己的值是不一樣的,這就產生了衝突。

能夠看到布隆過濾器有必定的誤識別率。下面咱們對其進行分析。

3、誤識別率的問題

假定布隆過濾器有m比特,裏面有n個元素,每一個元素對應k個信息指紋的哈希函數,固然m個比特里有0也有1。咱們假定某個比特爲0,在這個布隆過濾器裏插入一個元素,他的第一個哈希函數會把過濾器中的某個比特置爲1,理想狀況下,任一比特位被置1的機率是1/m,它依然爲0的機率則是1-1/m。

對於過濾器中的一個特定位置,若是這個元素的k個哈希函數都沒有把它設置成1,其機率是

。若是過濾器插入第二個元素,這個特定位置仍不被置1的機率是

,相似的,插入n個元素其仍爲0的機率是

。反過來,一個比特在插入n個元素後,被置1的機率則是

如今假定這n個元素都放到布隆過濾器中了,新來的一個不在集合中的元素,因爲它的信息指紋的哈希函數都是隨機的,所以,它的第一個哈希函數正好命中某個值爲1的比特的機率就是上述機率。一個再也不集合中的元素被誤識別爲已經在集合中,須要全部的哈希函數對應的比特值均爲1,其機率爲

咱們下面對簡化的誤識別率的公式進行研究。

圖2 誤識別率與m/n的關係

圖2的代碼:

k=8
r =  np.linspace(1,50,1000)
p = np.power(1-np.exp(-k/r),k)
plt.title('misjudgment & m/n')
plt.plot(r,p)
plt.xlabel('m/n')  
plt.ylabel('misjudgment')  
plt.show()

圖3 誤識別率與k的關係

圖3的代碼:

r = 30;
k = np.linspace(1,10,100)
p = np.power(1-np.exp(-k/r),k)
plt.title('misjudgment & k')
plt.xlabel('k')  
plt.ylabel('misjudgment')  
plt.plot(k,p)
plt.show()

首先恭喜您,可以認真的閱讀到這裏,若是對部分理解不太明白,建議先將文章收藏起來,而後對不清楚的知識點進行查閱,而後在進行閱讀,相應你會有更深的認知。若是您喜歡這篇文章,就點個贊或者【關注我】吧!!

相關文章
相關標籤/搜索