【數據結構】布隆過濾器原理詳解及其代碼實現

???《博主簡介》

小伙伴們好,我是阿旭。專注于人工智能AI、python、計算機視覺相關分享研究。
?更多學習資源,可關注公-仲-hao:【阿旭算法與機器學習】,共同學習交流~
👍感謝小伙伴們點贊、關注!

《------往期經典推薦------》

一、AI應用軟件開發實戰專欄【鏈接】
二、機器學習實戰專欄【鏈接】,已更新31期,歡迎關注,持續更新中~~
三、深度學習【Pytorch】專欄【鏈接】
四、【Stable Diffusion繪畫系列】專欄【鏈接】

布隆過濾器(Bloom Filter是一個占用空間很小、效率很高的隨機數據結構,它由一個bit數組和一組Hash算法構成。可用于判斷一個元素是否在一個集合中,查詢效率很高(1-N,最優能逼近于1)。

在很多場景下,我們都需要一個能迅速判斷一個元素是否在一個集合中。譬如:

網頁爬蟲對URL的去重,避免爬取相同的URL地址;

反垃圾郵件,從數十億個垃圾郵件列表中判斷某郵箱是否垃圾郵箱(同理,垃圾短信);

緩存擊穿,將已存在的緩存放到布隆中,當黑客訪問不存在的緩存時迅速返回避免緩存及DB掛掉。

可能有人會問,我們直接把這些數據都放到數據庫或者redis之類的緩存中不就行了,查詢時直接匹配不就OK了?

是的,當這個集合量比較小,你內存又夠大時,是可以這樣做,你可以直接弄個HashSet、HashMap就OK了。但是當這個量以數十億計,內存裝不下,數據庫檢索極慢時該怎么辦。

以垃圾郵箱為例

方案比較

1.將所有垃圾郵箱地址存到數據庫,匹配時遍歷

2.用HashSet存儲所有地址,匹配時接近O(1)的效率查出來

3.將地址用MD5算法或其他單向映射算法計算后存入HashSet,無論地址多大,保存的只有MD5后的固定位數

4.布隆過濾器,將所有地址經過多個Hash算法,映射到一個bit數組怎么判斷一個外來的元素是否已經在集合里呢如果映射的元素的中包含0,則該元素一定不在集合里,如果該元素映射的都為1,那么該元素可能在數組里。

優缺點

方案1和2都是保存完整的地址,占用空間大。一個地址16字節,10億即可達到上百G的內存。HashSet效率逼近O(1),數據庫就不談效率了,不在一個數量級。

方案3保存部分信息,占用空間小于存儲完整信息,存在沖突的可能(非垃圾郵箱可能MD5后和某垃圾郵箱一樣,概率低)

方案4將所有地址經過Hash后映射到?同一個bit數組,看清了,只有一個超大的bit數組,保存所有的映射,占用空間極小,沖突概率高。

大家知道,java中的HashMap有個擴容參數默認是0.75,也就是你想存75個數,至少需要一個100的數組,而且還會有不少的沖突。實際上,Hash的存儲效率是0.5左右,存5個數需要10個的空間。算起來占用空間還是挺大的。

而布隆過濾器就不用為每個數都分配空間了,而是直接把所有的數通過算法映射到同一個數組,帶來的問題就是沖突上升,只要概率在可以接受的范圍,用時間換空間,在很多時候是好方案。布隆過濾器需要的空間僅為HashMap的1/8-1/4之間,而且它不會漏掉任何一個在黑名單的可疑對象,問題只是會誤傷一些非黑名單對象。

原理

經過K個哈希算法將每個算法將元素映射到數組中的位置標1;

初始化狀態是一個全為0的bit數組

?

為了表達存儲N個元素的集合,使用K個獨立的函數來進行哈希運算。x1,x2……xk為k個哈希算法

如果集合元素有N1,N2……NN,N1經過x1運算后得到的結果映射的位置標1,經過x2運算后結果映射也標1,已經為1的1保持不變。經過k次散列后,對N1的散列完成。

依次對N2,NN等所有數據進行散列,最終得到一個部分為1,部分位為0的字節數組。當然了,這個字節數組會比較長,不然散列效果不好。

那么怎么判斷一個外來的元素是否已經在集合里呢,譬如已經散列了10億個垃圾郵箱,現在來了一個郵箱,怎么判斷它是否在這10億里面呢?

很簡單,就拿這個新來的也依次經歷x1,x2……xk個哈希算法即可。

在任何一個哈希算法譬如到x2時,得到的映射值有0,那就說明這個郵箱肯定不在這10億內。

如果是一個黑名單對象,那么可以肯定的是所有映射都為1,肯定跑不了它。也就是說是壞人,一定會被抓。

那么誤傷是為什么呢,就是指一些非黑名單對象的值經過k次哈希后,也全部為1,但它確實不是黑名單里的值,這種概率是存在的,但是是可控的。

什么情況下需要布隆過濾器?

先來看幾個比較常見的例子

  • 字處理軟件中,需要檢查一個英語單詞是否拼寫正確
  • 在 FBI,一個嫌疑人的名字是否已經在嫌疑名單上
  • 在網絡爬蟲里,一個網址是否被訪問過
  • yahoo, gmail等郵箱垃圾郵件過濾功能

這幾個例子有一個共同的特點:?如何判斷一個元素是否存在一個集合中?

常規思路

  • 數組
  • 鏈表
  • 樹、平衡二叉樹、Trie
  • Map (紅黑樹)
  • 哈希表

雖然上面描述的這幾種數據結構配合常見的排序、二分搜索可以快速高效的處理絕大部分判斷元素是否存在集合中的需求。但是當集合里面的元素數量足夠大,如果有500萬條記錄甚至1億條記錄呢?這個時候常規的數據結構的問題就凸顯出來了。數組、鏈表、樹等數據結構會存儲元素的內容,一旦數據量過大,消耗的內存也會呈現線性增長,最終達到瓶頸。有的同學可能會問,哈希表不是效率很高嗎?查詢效率可以達到O(1)。但是哈希表需要消耗的內存依然很高。使用哈希表存儲一億 個垃圾 email 地址的消耗?哈希表的做法:首先,哈希函數將一個email地址映射成8字節信息指紋;考慮到哈希表存儲效率通常小于50%(哈希沖突);因此消耗的內存:8 * 2 * 1億 字節 = 1.6G 內存,普通計算機是無法提供如此大的內存。這個時候,布隆過濾器(Bloom Filter)就應運而生。在繼續介紹布隆過濾器的原理時,先講解下關于哈希函數的預備知識。

哈希函數

哈希函數的概念是:將任意大小的數據轉換成特定大小的數據的函數,轉換后的數據稱為哈希值或哈希編碼。下面是一幅示意圖:

可以明顯的看到,原始數據經過哈希函數的映射后稱為了一個個的哈希編碼,數據得到壓縮。哈希函數是實現哈希表和布隆過濾器的基礎。

布隆過濾器介紹

  • 巴頓.布隆于一九七零年提出
  • 一個很長的二進制向量 (位數組)
  • 一系列隨機函數 (哈希)
  • 空間效率和查詢效率高
  • 有一定的誤判率(哈希表是精確匹配)

布隆過濾器原理

布隆過濾器(Bloom Filter)的核心實現是一個超大的位數組和幾個哈希函數。假設位數組的長度為m,哈希函數的個數為k

以上圖為例,具體的操作流程:假設集合里面有3個元素{x, y, z},哈希函數的個數為3。首先將位數組進行初始化,將里面每個位都設置位0。對于集合里面的每一個元素,將元素依次通過3個哈希函數進行映射,每次映射都會產生一個哈希值,這個值對應位數組上面的一個點,然后將位數組對應的位置標記為1。查詢W元素是否存在集合中的時候,同樣的方法將W通過哈希映射到位數組上的3個點。如果3個點的其中有一個點不為1,則可以判斷該元素一定不存在集合中。反之,如果3個點都為1,則該元素可能存在集合中。注意:此處不能判斷該元素是否一定存在集合中,可能存在一定的誤判率。可以從圖中可以看到:假設某個元素通過映射對應下標為4,5,6這3個點。雖然這3個點都為1,但是很明顯這3個點是不同元素經過哈希得到的位置,因此這種情況說明元素雖然不在集合中,也可能對應的都是1,這是誤判率存在的原因。

布隆過濾器添加元素

  • 將要添加的元素給k個哈希函數
  • 得到對應于位數組上的k個位置
  • 將這k個位置設為1

布隆過濾器查詢元素

  • 將要查詢的元素給k個哈希函數
  • 得到對應于位數組上的k個位置
  • 如果k個位置有一個為0,則肯定不在集合中
  • 如果k個位置全部為1,則可能在集合中

布隆過濾器實現

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]

關于本篇文章大家有任何建議或意見,歡迎在評論區留言交流!

覺得不錯的小伙伴,感謝點贊、關注加收藏哦!

歡迎關注下方GZH:阿旭算法與機器學習,共同學習交流~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/382812.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/382812.shtml
英文地址,請注明出處:http://en.pswp.cn/news/382812.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

c++詳解【繼承】

學過c的人都知道,c的三大特性:封裝、繼承、多態。 我們今天說的是c的繼承,那么為什么要引入繼承,它有什么特點呢? 首先,繼承的特點是:使代碼復用,為后面學習多態做鋪墊。 繼承分為…

centOS6.5如何從啟動界面直接進入命令行界面和如何從圖形界面進入命令行界面

centOS6.5如何從啟動界面直接進入命令行界面 編輯 /etc/inittab 將 id:5:initdefault: 修改為 id:3:initdefault: 下次重啟就不啟動X Window了 如何從圖形界面進入命令行界面 startx

優酷解析 轉載的

轉自 https://blog.csdn.net/qq_39797956/article/details/88076404

【送給Git初學者】

好多人都聽過Git吧,目前最流行的分布式版本管理系統。還有好多類似的cvs、svn(速度慢、必須聯網,這些是集中式版本控制系統)..... 那么,它是用來干什么的呢?舉個例子可能更好理解吧! 比如你寫…

虛擬機中的Linux安裝VMware Tools的方法

虛擬機中的Linux安裝VMware Tools的方法 http://www.jb51.net/softjc/189144.html 當.pl文件無法執行時 chmod install-vmware.pl./ install-vmware.pl 安裝就可。 先以root身份登入。 VMware Tools所在位置:VMware 安裝路徑 \VMware\VMware Workstation\linux…

appium 設置參數

appium 配置好環境變量以后, 需要設置啟動參數, 設備名稱, 應用的一些信息主要有以下信息: {"platformName": "Android","platformVersion": "5.1.1","deviceName": "ee…

遠程倉庫

上節我們安裝好了git,并配置好git,github之間的ssh。這節我們就開始用git管理我們的倉庫吧。(這節在windows下安裝的git bash上給大家演示吧) 首先,創建好一個倉庫,主要步驟如下: 創建好倉庫后…

linux根目錄的意義和內容

1.du命令:du [選項] 文件 ????(1)功能該命令是顯示指定文件以及下的所有文件占用系統數據塊的情況,如果沒有文件,默認為是當前工作目錄 ????-a ???顯示所有文件對系統數據塊的使用情況 ????-b ???顯示數據塊大小時以字節…

c++詳解【智能指針】

智能指針?是一個指針嗎?這里給大家說的是,它不是一個指針,但它模擬了指針所具有的功能。那么,為什么要有智能指針的引入呢?看看下面的例子吧~ void FunTest() {int *p new int[10];FILE *pFile fopen(&qu…

python 使用 os的 popen(‘命令’) 如果命令行輸出中 有中文亂碼, 提示 'gbk' 無法解析的錯誤 解決辦法

os.chdir(‘你的命令’) res os.popen(v.testcomman)print(tempstream.buffer.read().decode(encodingutf-8))

node.js async await 配合Promise對象使用

function getData(){return new Promise(function(resolve, reject){setTimeout(function(){var uname zhang;console.log(this is timeout);resolve(uname);}, 1000);}); } //await 配合 promiese 的 resolve 使用 就會真的等待 同步 async function test(){console.log(1);v…

c++【深度剖析shared_ptr】

shared_ptr解決了scoped_ptr管理單個對象的缺陷,且解決了防拷貝的問題。shared_ptr可以管理多個對象,并且實現了資源共享。 但是仍然存在一些問題,比如,我們熟悉的雙向鏈表: struct Node { Node(const int& value…

centos重新安裝yum

1.備份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2.下載新的CentOS-Base.repo 到/etc/yum.repos.d/ wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo 3. yum makecache GDB的安裝 yum…

Electron 渲染進程,如何解決require is not defined的問題

mainWindow new BrowserWindow({webPreferences: {nodeIntegration: true}}) // nodeIntegration: true 加上這一句 就可以了 5.0以后默認是false

c++詳解【new和delete】

說起new和delete,了解過c的人應該都知道吧,它是用來分配內存和釋放內存的兩個操作符。與c語言中的malloc和free類似。 c語言中使用malloc/calloc/realloc/free進行動態內存分配,malloc/calloc/realloc用來在堆上分配空間,free將申…

vim 的配置文件 #vim ~/.vimrc

set hlsearch set backspace2 set nu set showmode set ruler set autoindent syntax on set smartindent set tabstop4 set shiftwidth4 set expandtab imap { {}iV

關于tornado的異步耗時操作假設

tornado 如果遇到耗時的操作,可不可以這樣 把耗時操作放在一個由 python進程池維護的 pool中, 用 webapi封裝起來, 然后tornado 接收客戶端請求后,遇到耗時操作就 與訪問另一個webapi , webapi去調用進程池 這種模型不…

Stack/Queue與Vector/List的聯系

Vector:(順序表【數組存儲】) 1.當申請的空間不足的時候,需要再次開辟一塊更大的空間,并把值拷過去。 2.對于尾刪和尾插是比較方便的,只需要改動最后一個元素即可。不會改動原有的空間。適用于多次重復的對尾部插刪。 3.順序存儲&#xff…

利用SetConsoleTextAttribute函數設置控制臺顏色

原文出處&#xff1a; https://blog.csdn.net/odaynot/article/details/7722240 混合顏色 #include <windows.h> #include <iostream> using namespace std;int main() {HANDLE hOut;hOut GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(hOut,FOREG…