Python与Redis实现布隆过滤器的原理、优缺点及代码详解

Aria
Aria
发布时间:2026-10-13 | 6人浏览过
原创

本文深入解析布隆过滤器(Bloom Filter)的原理、优缺点及在Python和Redis中的实现。重点讨论空间效率、误报率控制机制及哈希函数选择,提供基于Python和Redis的完整代码示例,帮助开发者理解如何平衡查询性能与内存占用,适用于大规模数据去重与缓存穿透防护场景。

布隆过滤器概述

布隆过滤器是什么

布隆过滤器(Bloom Filter)由布隆于1970年提出,本质上是一个长二进制向量配合一系列随机映射函数构成的数据结构。它主要用于判断某个元素是否存在于集合中。相比常规算法,布隆过滤器在空间利用率和查询速度上具有显著优势,但代价是存在一定的误判率且难以支持删除操作。

布隆过滤器的基本思想

其核心机制依赖于散列表(Hash table)。通过特定的Hash函数,将待处理元素映射到位阵列(Bit array)中的特定位点。只需检查该位点是否为1,即可初步判断元素是否存在于集合中。

布隆过滤器的优缺点

优点:

1. 布隆过滤器的存储空间占用以及插入和查询的时间复杂度均保持为常数级别。

2. 各Hash函数之间相互独立,便于在硬件层面实现并行处理。

3. 布隆过滤器无需存储元素的具体内容,这在某些对数据保密性要求极高的场景中具备独特优势。

4. 布隆过滤器能够表示全集概念,这是其他任何数据结构难以做到的。

缺点:

1. 存在固有的误算率,且随着存入元素数量的增加,误算率会随之上升。

(常见的应对策略是建立一个小规模的白名单,用于存储那些可能被误判的元素。但如果需要存储的元素数量很少,直接使用散列表可能更为合适)。

2. 通常情况下,从布隆过滤器中删除元素是非常困难的。

首先,必须确保被删除的元素确实存在于布隆过滤器中,而仅凭过滤器本身无法验证这一点。此外,计数器回绕问题也会引发异常,导致删除操作的成本极高。

Python基础实现与结果分析

正文

简单的python实现

pip install mmh3

若遇到安装报错或C++编译错误,建议安装Microsoft Visual C++ Build Tools以解决依赖问题。

示例代码参考自:https://www.jb51.net/article/175929.htm


from bitarray import bitarray
# 3rd party
import mmh3
class BloomFilter(set):
 def __init__(self, size, hash_count):
 super(BloomFilter, self).__init__()
 self.bit_array = bitarray(size)
 self.bit_array.setall(0)
 self.size = size
 self.hash_count = hash_count
 def __len__(self):
 return self.size
 def __iter__(self):
 return iter(self.bit_array)
 def add(self, item):
 for ii in range(self.hash_count):
  index = mmh3.hash(item, ii) % self.size
  self.bit_array[index] = 1
 return self
 def __contains__(self, item):
 out = True
 for ii in range(self.hash_count):
  index = mmh3.hash(item, ii) % self.size
  if self.bit_array[index] == 0:
  out = False
 return out
def main():
 bloom = BloomFilter(10000, 10)
 animals = ['dog', 'cat', 'giraffe', 'fly', 'mosquito', 'horse', 'eagle',
  'bird', 'bison', 'boar', 'butterfly', 'ant', 'anaconda', 'bear',
  'chicken', 'dolphin', 'donkey', 'crow', 'crocodile']
 # First insertion of animals into the bloom filter
 for animal in animals:
 bloom.add(animal)
 # Membership existence for already inserted animals
 # There should not be any false negatives
 for animal in animals:
 if animal in bloom:
  print('{} is in bloom filter as expected'.format(animal))
 else:
  print('Something is terribly went wrong for {}'.format(animal))
  print('FALSE NEGATIVE!')
 # Membership existence for not inserted animals
 # There could be false positives
 other_animals = ['badger', 'cow', 'pig', 'sheep', 'bee', 'wolf', 'fox',
   'whale', 'shark', 'fish', 'turkey', 'duck', 'dove',
   'deer', 'elephant', 'frog', 'falcon', 'goat', 'gorilla',
   'hawk' ]
 for other_animal in other_animals:
 if other_animal in bloom:
  print('{} is not in the bloom, but a false positive'.format(other_animal))
 else:
  print('{} is not in the bloom filter as expected'.format(other_animal))
if __name__ == '__main__':
 main()

运行结果

dog is in bloom filter as expected cat is in bloom filter as expected giraffe is in bloom filter as expected fly is in bloom filter as expected mosquito is in bloom filter as expected horse is in bloom filter as expected eagle is in bloom filter as expected bird is in bloom filter as expected bison is in bloom filter as expected boar is in bloom filter as expected butterfly is in bloom filter as expected ant is in bloom filter as expected anaconda is in bloom filter as expected bear is in bloom filter as expected chicken is in bloom filter as expected dolphin is in bloom filter as expected donkey is in bloom filter as expected crow is in bloom filter as expected crocodile is in bloom filter as expected

badger is not in the bloom filter as expected cow is not in the bloom filter as expected pig is not in the bloom filter as expected sheep is not in the bloom, but a false positive bee is not in the bloom filter as expected wolf is not in the bloom filter as expected fox is in the bloom filter as expected whale is in the bloom filter as expected shark is not in the bloom, but a false positive fish is not in the bloom, but a false positive turkey is not in the bloom filter as expected duck is not in the bloom filter as expected dove is in the bloom误报 filter as expected deer is not in the bloom filter as expected elephant is not in the bloom, but a false positive frog is not in the bloom filter as expected falcon is not in the bloom filter as expected goat is not in the bloom filter as expected gorilla is not in the bloom filter as expected hawk is not in the bloom filter as expected

从输出结果可以看出,系统中存在不少误报样本,但并未出现假阴性(即已存在元素被判定为不存在的情况)。

与这段布隆过滤器的简单实现不同,其他语言的多个实现版本通常不提供直接设置哈希函数数量的参数。这是因为在实际应用中,误报比例这一指标比哈希函数的具体数量更为关键。用户可以根据对误报比例的容忍度来调整哈希函数的个数。通常来说,size(位数组大小)和error_rate(错误率)是决定布隆过滤器实际误报比例的关键因素。如果在初始化阶段降低了error_rate,系统会自动调整哈希函数的数量以适应新的要求。

误报机制与确定性分析

误报

布隆过滤器可以自信地断言某个元素“肯定不存在”,但对于某些元素,它只能回答“可能存在”。针对不同的应用场景,这既可能是一个巨大的缺陷,也可能是可以忽略的问题。如果在检索元素是否存在时不介意引入误报情况,那么布隆过滤器是一个值得考虑的选择。

另外,如果随意减小误报比率,哈希函数的数量相应增加,插入和查询时的延迟也会随之上升。本节另一个要点是,如果哈希函数相互独立且输入元素在空间中均匀分布,理论上真实误报率不会超过理论值。否则,由于哈希函数的相关性和更频繁的哈希冲突,布隆过滤器的真实误报比例可能会高于理论值。

在使用布隆过滤器时,必须充分评估误报带来的潜在影响。

确定性

当使用相同大小和数量的哈希函数时,某个元素通过布隆过滤器得到的正反馈或负反馈结果是确定的。对于某个元素x,如果它当前“可能存在”,那么五分钟后、一小时后、一天后甚至一周后,其状态依然是“可能存在”。得知这一特性时令人惊讶,因为布隆过滤器是概率性的,结果似乎应包含随机因素。然而事实并非如此。它的概率性体现在我们无法预先判断究竟哪些元素的状态是“可能存在”,而非结果本身随机变化。

换句话说,过滤器一旦做出“可能存在”的结论,该结论不会随时间改变。

基于Redis的高级实现

python 基于redis实现的bloomfilter(布隆过滤器),BloomFilter_imooc

BloomFilter_imooc下载

下载地址:https://github.com/liyaopinner/BloomFilter_imooc

py_bloomfilter.py(布隆过滤器)源码:


import mmh3
import redis
import math
import time
class PyBloomFilter():
 #内置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]
 #capacity是预先估计要去重的数量
 #error_rate表示错误率
 #conn表示redis的连接客户端
 #key表示在redis中的键的名字前缀
 def __init__(self, capacity=1000000000, error_rate=0.00000001, conn=None, key='BloomFilter'):
 self.m = math.ceil(capacity*math.log2(math.e)*math.log2(1/error_rate)) #需要的总bit位数
 self.k = math.ceil(math.log1p(2)*self.m/capacity)    #需要最少的hash次数
 self.mem = math.ceil(self.m/8/1024/1024)     #需要的多少M内存
 self.blocknum = math.ceil(self.mem/512)     #需要多少个512M的内存块,value的第一个字符必须是ascii码,所有最多有256个内存块
 self.seeds = self.SEEDS[0:self.k]
 self.key = key
 self.N = 2**31-1
 self.redis = conn
 # print(self.mem)
 # print(self.k)
 def add(self, value):
 name = self.key + "_" + str(ord(value[0])%self.blocknum)
 hashs = self.get_hashs(value)
 for hash in hashs:
  self.redis.setbit(name, hash, 1)
 def is_exist(self, value):
 name = self.key + "_" + str(ord(value[0])%self.blocknum)
 hashs = self.get_hashs(value)
 exist = True
 for hash in hashs:
  exist = exist & self.redis.getbit(name, hash)
 return exist
 def get_hashs(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
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=0)
conn = redis.StrictRedis(connection_pool=pool)
# 使用方法
# if __name__ == "__main__":
# bf = PyBloomFilter(conn=conn)  # 利用连接池连接Redis
# bf.add('www.jobbole.com')  # 向Redis默认的通道添加一个域名
# bf.add('www.luyin.org')   # 向Redis默认的通道添加一个域名
# print(bf.is_exist('www.zhihu.com')) # 打印此域名在通道里是否存在,存在返回1,不存在返回0
# print(bf.is_exist('www.luyin.org')) # 打印此域名在通道里是否存在,存在返回1,不存在返回0

总结

以上所述是小编给大家介绍的Python+Redis实现布隆过滤器,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表