python中的布隆過濾器用法及原理詳解
1、布隆過濾器的介紹
布隆過濾器(Bloom Filter),是1970年,由一個叫布隆的小伙子提出的。 它實際上是一個很長的二進制向量和一系列隨機映射函數(shù),二進制大家應(yīng)該都清楚,存儲的數(shù)據(jù)不是0就是1,默認是0。
主要用于判斷一個元素是否在一個集合中,0代表不存在某個數(shù)據(jù),1代表存在某個數(shù)據(jù)。
布隆過濾器是一種概率空間高效的數(shù)據(jù)結(jié)構(gòu),特點是高效地插入和查詢,用來告訴你 “某樣?xùn)|西一定不存在或者可能存在”。
相比于傳統(tǒng)的 List、Set、Map 等數(shù)據(jù)結(jié)構(gòu),它更高效、占用空間更少,但是缺點是其返回的結(jié)果是概率性的,而不是確切的。
2、布隆過濾器的用途
- 解決Redis緩存穿透。
- 在爬蟲時,對爬蟲網(wǎng)址進行過濾,已經(jīng)存在布隆中的網(wǎng)址,不在爬取。
- 垃圾郵件過濾,對每一個發(fā)送郵件的地址進行判斷是否在布隆的黑名單中,如果在就判斷為垃圾郵件。
- 大量數(shù)據(jù)時,判斷給定的數(shù)據(jù)是否在其中。
- 黑名單過濾
3、布隆過濾器的原理
3.1 存入過程
就是將數(shù)據(jù)以某種方式以二進制形式存入結(jié)合中。
過程如下:
- 通過K個哈希函數(shù)計算該數(shù)據(jù),返回K個計算出的hash值
- 這些K個hash值映射到對應(yīng)的K個二進制的數(shù)組下標
- 將K個下標對應(yīng)的二進制數(shù)據(jù)改成1。
例如,第一個哈希函數(shù)返回x,第二個第三個哈希函數(shù)返回y與z,那么: X、Y、Z對應(yīng)的二進制改成1。
3.2 查詢過程
布隆過濾器主要作用就是:查詢一個數(shù)據(jù)在不在這個二進制的集合中。
查詢過程如下:
- 通過K個哈希函數(shù)計算該數(shù)據(jù),對應(yīng)計算出的K個hash值
- 通過hash值找到對應(yīng)的二進制的數(shù)組下標
- 判斷:如果存在一處位置的二進制數(shù)據(jù)是0,那么該數(shù)據(jù)不存在。如果都是1,該數(shù)據(jù)存在集合中。(這種判斷存在一定的誤判率)
3.3 刪除過程
一般是不能刪除布隆過濾器的,這也是其一個缺點。
4、布隆過濾器的優(yōu)缺點
4.1 優(yōu)點
- 由于存儲的是二進制數(shù)據(jù),所以占用的空間很小
- 它的插入和查詢速度是非??斓?,時間復(fù)雜度是O(K),可以聯(lián)想一下HashMap的過程
- 保密性很好,因為本身不存儲任何原始數(shù)據(jù),只有二進制數(shù)據(jù)
4.2 缺點
添加數(shù)據(jù)是通過計算數(shù)據(jù)的hash值,那么很有可能存在這種情況:兩個不同的數(shù)據(jù)計算得到相同的hash值。
例如圖中的“你好”和“hello”,假如最終算出hash值相同,那么他們會將同一個下標的二進制數(shù)據(jù)改為1。 這個時候,你就不知道下標為2的二進制,到底是代表“你好”還是“hello”。
1.存在誤判率
假如上面的圖沒有存"hello",只存了"你好",那么用"hello"來查詢的時候,會判斷"hello"存在集合中。 因為“你好”和“hello”的hash值是相同的,通過相同的hash值,找到的二進制數(shù)據(jù)也是一樣的,都是1。
2.刪除困難
還是用上面的舉例,因為“你好”和“hello”的hash值相同,對應(yīng)的數(shù)組下標也是一樣的。 這時候想去刪除“你好”,將下標為2里的二進制數(shù)據(jù),由1改成了0。 那么我們是不是連“hello”都一起刪了呀。(0代表有這個數(shù)據(jù),1代表沒有這個數(shù)據(jù))
總結(jié):
- 隨著數(shù)據(jù)的增加,誤判率隨之增加;
- 無法做到刪除數(shù)據(jù);只能判斷數(shù)據(jù)是否一定不存在,而無法判斷數(shù)據(jù)是否一定存在。
- 無法返回元素本身
- 無法刪除某個元素
5、python中使用布隆過濾器
使用pybloom_live進行操作。
安裝:
pip install pybloom_live
示例代碼:
from pybloom_live import ScalableBloomFilter, BloomFilter # 可自動擴容的布隆過濾器 bloom = ScalableBloomFilter(initial_capacity=100, error_rate=0.001) url1 = 'http://www.baidu.com' url2 = 'http://www.zhihu.com' bloom.add(url1) print(url1 in bloom) print(url2 in bloom) # BloomFilter 是定長的 bf = BloomFilter(capacity=1000) bf.add(url1) print(url1 in bf) print(url2 in bf)
運行結(jié)果:
6、redis中使用布隆過濾器
詳細的文檔可以參考官方文檔:Quick start | Redis
這個模塊不僅僅實現(xiàn)了布隆過濾器,還實現(xiàn)了 CuckooFilter(布谷鳥過濾器),以及 TopK功能。CuckooFilter是在 BloomFilter的基礎(chǔ)上主要解決了BloomFilter不能刪除的缺點。
傳統(tǒng)的redis服務(wù)器安裝 RedisBloom 插件
也可以使用docker進行安裝:
- docker pull redislabs/rebloom:latest
- docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
- docker exec -it redis-redisbloom /bin/bash
使用pybloom_live進行操作。
安裝:
pip install redisbloom
示例代碼: 【注意:代碼執(zhí)行之前要確保服務(wù)器安裝了redis插件:bloom-filter】
from redisbloom.client import Client rb = Client(host='192.168.124.27', port='6379') rb.bfAdd('urls', 'baidu') rb.bfAdd('urls', 'google') print(rb.bfExists('urls', 'baidu')) print(rb.bfExists('urls', 'tencent')) rb.bfMAdd('urls', 'a', 'b') print(rb.bfMExists('urls', 'google', 'a', 'd'))
運行結(jié)果:
示例代碼:
import math import redis import time import mmh3 class BloomFilter(object): # 內(nèi)置100個隨機種子 SEEDS = [543, 460, 171, 876, 796, 607, 650, 81, 837, 545, 591, 946, 846, 521, 913, 636, 878, 735, 414, 372, 344, 324, 223, 180, 327, 891, 798, 933, 493, 293, 836, 10, 6, 544, 924, 849, 438, 41, 862, 648, 338, 465, 562, 693, 979, 52, 763, 103, 387, 374, 349, 94, 384, 680, 574, 480, 307, 580, 71, 535, 300, 53, 481, 519, 644, 219, 686, 236, 424, 326, 244, 212, 909, 202, 951, 56, 812, 901, 926, 250, 507, 739, 371, 63, 584, 154, 7, 284, 617, 332, 472, 140, 605, 262, 355, 526, 647, 923, 199, 518] def __init__(self, capacity=1000000000, error_rate=0.00000001, conn=None, key='BloomFilter'): """ 初始化布隆過濾器 :param capacity: 預(yù)先估計要去重的數(shù)量 :param error_rate: 表示錯誤率 :param conn: 表示redis的連接客戶端 :param key: 表示在redis中的鍵的名字前綴 """ # 需要的總bit位數(shù) self.m = math.ceil(capacity*math.log2(math.e)*math.log2(1/error_rate)) # 需要最少的hash次數(shù) self.k = math.ceil(math.log1p(2)*self.m/capacity) # 需要的多少M內(nèi)存 self.mem = math.ceil(self.m/8/1024/1024) # 需要多少個512M的內(nèi)存塊,value的第一個字符必須是ascii碼,所有最多有256個內(nèi)存塊 self.blocknum = math.ceil(self.mem/512) self.seeds = self.SEEDS[0: self.k] self.key = key self.N = 2 ** 31 - 1 self.redis = conn print(self.m) print(self.k) print(self.mem) def add(self, value): name = self.key + '_' + str(ord(value[0]) % self.blocknum) hashs = self.get_hash(value) for hash in hashs: self.redis.setbit(name, hash, 1) def get_hash(self, value): hashs = list() for seed in self.seeds: hash = mmh3.hash(value, seed) if hash >= 0: hashs.append(hash) else: hashs.append(self.N - hash) return hashs def is_exist(self, value): name = self.key + '_' + str(ord(value[0]) % self.blocknum) hashs = self.get_hash(value) exist = True for hash in hashs: exist = exist & self.redis.getbit(name, hash) return exist pool = redis.ConnectionPool(host='192.168.124.27', port='6379', db=0) conn = redis.Redis(connection_pool=pool) start = time.time() bf = BloomFilter(conn=conn) url1 = 'www.baidu.com' url2 = 'www.zhihu.com' url3 = 'www.study.com' bf.add(url1) bf.add(url2) print(bf.is_exist(url2)) print(bf.is_exist(url3))
運行結(jié)果:
到此這篇關(guān)于python中的布隆過濾器用法及原理詳解的文章就介紹到這了,更多相關(guān)python的布隆過濾器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
9行Python3代碼實現(xiàn)批量提取PDF文件的指定內(nèi)容
這篇文章主要為大家詳細介紹了如何通過9行Python3代碼實現(xiàn)批量提取PDF文件的指定內(nèi)容,文中的示例代碼講解詳細,感興趣的小伙伴可以嘗試一下2022-12-12python 在右鍵菜單中加入復(fù)制目標文件的有效存放路徑(單斜杠或者雙反斜杠)
這篇文章主要介紹了python 在右鍵菜單中加入復(fù)制目標文件的有效存放路徑(單斜杠或者雙反斜杠),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04