布隆过滤器

布隆过滤器是一种概率空间高效的数据结构。它与hashmap非常相似,用于检索一个元素是否在一个集合中。它在检索元素是否存在时,能很好地取舍空间使用率与误报比例。正是由于这个特性,它被称作概率性数据结构(probabilistic data structure)。

空间效率

我们来仔细地看看它的空间效率。如果你想在集合中存储一系列的元素,有很多种不同的做法。你可以把数据存储在hashmap,随后在hashmap中检索元素是否存在,hashmap的插入和查询的效率都非常高。但是,由于hashmap直接存储内容,所以空间利用率并不高。

如果希望提高空间利用率,我们可以在元素插入集合之前做一次哈希变换。还有其它方法呢?我们可以用位数组来存储元素的哈希值。还有吗,还有吗?我们也允许在位数组中存在哈希冲突。这正是布隆过滤器的工作原理,它们就是基于允许哈希冲突的位数组,可能会造成一些误报。在布隆过滤器的设计阶段就允许哈希冲突的存在,否则空间使用就不够紧凑了。

当使用列表或者集合时,空间效率都是重要且显著的,那么布隆过滤器就应当被考虑。

布隆过滤器基础

布隆过滤器是N位的位数组,其中N是位数组的大小。它还有另一个参数k,表示使用哈希函数的个数。这些哈希函数用来设置位数组的值。当往过滤器中插入元素x时,h1(x), h2(x), ..., hk(x)所对应索引位置的值被置“1”,索引值由各个哈希函数计算得到。注意,如果我们增加哈希函数的数量,误报的概率会趋近于0.但是,插入和查找的时间开销更大,布隆过滤器的容量也会减小。

为了用布隆过滤器检验元素是否存在,我们需要校验是否所有的位置都被置“1”,与我们插入元素的过程非常相似。如果所有位置都被置“1”,那也就意味着该元素很有可能存在于布隆过滤器中。若有位置未被置“1”,那该元素一定不存在。

简单的python实现

如果想实现一个简单的布隆过滤器,我们可以这样做:

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(100, 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 not in the bloom filter as expected
whale is not 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 not 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

从输出结果可以发现,存在不少误报样本,但是并不存在假阴性。

不同于这段布隆过滤器的实现代码,其它语言的多个实现版本并不提供哈希函数的参数。这是因为在实际应用中误报比例这个指标比哈希函数更重要,用户可以根据误报比例的需求来调整哈希函数的个数。通常来说,sizeerror_rate是布隆过滤器的真正误报比例。如果你在初始化阶段减小了error_rate,它们会调整哈希函数的数量。

误报

布隆过滤器能够拍着胸脯说某个元素“肯定不存在”,但是对于一些元素它们会说“可能存在”。针对不同的应用场景,这有可能会是一个巨大的缺陷,亦或是无关紧要的问题。如果在检索元素是否存在时不介意引入误报情况,那么你就应当考虑用布隆过滤器。

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

在使用布隆过滤器时,需要考虑误报的潜在影响。

确定性

当你使用相同大小和数量的哈希函数时,某个元素通过布隆过滤器得到的是正反馈还是负反馈的结果是确定的。对于某个元素x,如果它现在可能存在,那五分钟之后、一小时之后、一天之后、甚至一周之后的状态都是可能存在。当我得知这一特性时有一点点惊讶。因为布隆过滤器是概率性的,那其结果显然应该存在某种随机因素,难道不是吗?确实不是。它的概率性体现在我们无法判断究竟哪些元素的状态是可能存在

换句话说,过滤器一旦做出可能存在的结论后,结论不会发生变化。

缺点

布隆过滤器并不十全十美。

布隆过滤器的容量

布隆过滤器需要事先知道将要插入的元素个数。如果你并不知道或者很难估计元素的个数,情况就不太好。你也可以随机指定一个很大的容量,但这样就会浪费许多存储空间,存储空间却是我们试图优化的首要任务,也是选择使用布隆过滤器的原因之一。一种解决方案是创建一个能够动态适应数据量的布隆过滤器,但是在某些应用场景下这个方案无效。有一种可扩展布隆过滤器,它能够调整容量来适应不同数量的元素。它能弥补一部分短板。

布隆过滤器的构造和检索

在使用布隆过滤器时,我们不仅要接受少量的误报率,还要接受速度方面的额外时间开销。相比于hashmap,对元素做哈希映射和构建布隆过滤器时必然存在一些额外的时间开销。

无法返回元素本身

布隆过滤器并不会保存插入元素的内容,只能检索某个元素是否存在,因为存在哈希函数和哈希冲突我们无法得到完整的元素列表。这是它相对于其它数据结构的最显著优势,空间的使用率也造成了这块短板。

删除某个元素

想从布隆过滤器中删除某个元素可不是一件容易的事情,你无法撤回某次插入操作,因为不同项目的哈希结果可以被索引在同一位置。如果想撤消插入,你只能记录每个索引位置被置位的次数,或是重新创建一次。两种方法都有额外的开销。基于不同的应用场景,若要删除一些元素,我们更倾向于重建布隆过滤器。

在不同语言中的实现

在产品中,你肯定不想自己去实现布隆过滤器。有两个原因,其中之一是选择好的哈希函数和实现方法能有效改善错误率的分布。其次,它需要通过实战测试,错误率和容量大小都要经得起实战检验。各种语言都有开源实现的版本,以我自己的经验,下面的Node.js和Python版本实现非常好用:

还有更快版本的pybloomfilter(插入和检索速度都比上面的python库快10倍),但它需要运行在PyPy环境下,并不支持Python3。

布隆过滤器的概述及Python实现的更多相关文章

  1. Python爬虫学习——布隆过滤器

    布隆过滤器的实现方法1:自己实现 参考 http://www.cnblogs.com/naive/p/5815433.html bllomFilter两个参数分别代表,布隆过滤器的大小和hash函数的 ...

  2. 第三百五十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中

    第三百五十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中,判断URL是否重复 布隆过滤器(Bloom Filter)详 ...

  3. 三十七 Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中

    Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中,判断URL是否重复 布隆过滤器(Bloom Filter)详解 基本概念 如 ...

  4. 布隆过滤器(Bloom Filters)的原理及代码实现(Python + Java)

    本文介绍了布隆过滤器的概念及变体,这种描述非常适合代码模拟实现.重点在于标准布隆过滤器和计算布隆过滤器,其他的大都在此基础上优化.文末附上了标准布隆过滤器和计算布隆过滤器的代码实现(Java版和Pyt ...

  5. python实现布隆过滤器及原理解析

    python实现布隆过滤器及原理解析     布隆过滤器( BloomFilter )是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地 ...

  6. 布隆过滤器(Bloom Filter)的原理和实现

    什么情况下需要布隆过滤器? 先来看几个比较常见的例子 字处理软件中,需要检查一个英语单词是否拼写正确 在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上 在网络爬虫里,一个网址是否被访问过 yahoo, ...

  7. 浅析布隆过滤器及实现demo

    布隆过滤器 布隆过滤器(Bloom Filter)是一种概率空间高效的数据结构.它与hashmap非常相似,用于检索一个元素是否在一个集合中.它在检索元素是否存在时,能很好地取舍空间使用率与误报比例. ...

  8. 将bloomfilter(布隆过滤器)集成到scrapy-redis中

    Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中,判断URL是否重复 布隆过滤器(Bloom Filter)详解 基本概念 如 ...

  9. 基于Redis扩展模块的布隆过滤器使用

    什么是布隆过滤器?它实际上是一个很长的二进制向量和一系列随机映射函数.把一个目标元素通过多个hash函数的计算,将多个随机计算出的结果映射到不同的二进制向量的位中,以此来间接标记一个元素是否存在于一个 ...

随机推荐

  1. Nginx配置单主机多域名

    http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; ...

  2. java 中关于synchronized的通常用法

    package j2se.thread.test; /*** * synchronized(class)很特别,它会让另一个线程在任何需要获取class做为monitor的地方等待. * class与 ...

  3. svn图标不显示的解决方案

    最近发现svn图标莫名其妙的不显示,其他操作都正常.在网上搜了一些方法. 解决方法一(失败): 升级最新版本,我的本来就是最新版本 解决方法二(失败): 右键->TortoiseSVN-> ...

  4. eap-md5

    eap-md5       文件路径 用途 示例 备注 #gedit /usr/local/etc/raddb/sites-available/default #gedit /usr/local/et ...

  5. 一个Java递归删除目录的方法

    public static void delDir(File f) { // 判断是否是一个目录, 不是的话跳过, 直接删除; 如果是一个目录, 先将其内容清空. if(f.isDirectory() ...

  6. excel 转换日期

    早上一朋友问我excel中如何将类似这样“19850421”的文本日期转换为“1985-04-21”.我的第一反应就是直接设置单元格格式为日期,于是打开excel试了试结果显示“########### ...

  7. 【python】3.x,string与bytes的区别(文本,二进制数据)

    Python 3对文本和二进制数据作了更为清晰的区分.文本总是Unicode,由str类型表示, 二进制数据则由bytes类型表示. 不能拼接字符串和字节包,也无法在字节包里搜索字符串(反之亦然),也 ...

  8. knockout.js 简介

    Knockout是一个轻量级的UI类库,通过应用MVVM模式使JavaScript前端UI简单化. Knockout有如下4大重要概念: 声明式绑定 (Declarative Bindings):使用 ...

  9. C语言程序设计第8堂作业

    一.本次课主要内容: 本次课通过以下两个知识点来完成: (1)以数字金字塔为例,介绍函数的另一种形式,即不返回结果的函数.不返回结果的函数在定义.调用.参数传递.函数声明上,思路完全与以前相同,只是函 ...

  10. android studio 2.2.2下fragment的创建和跳转

    一,首先,Fragment是android应用中十分重要的一个功能,十分轻量化,也类似于activity一样,是一个个布局,可以相互跳转和传递参数.但是,它运行起来十分流畅,而且易于管理,下面是在学习 ...