1、基本概念
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它其实是一个很长的二进制向量和一系列随机映射函数。布隆过滤器能够用于检索一个元素是否在一个集合中。
它的优势是空间效率和查询时间都远远超过通常的算法,缺点是有必定的误识别率和删除困难。
Google爬虫它要判断。哪些网页是被爬过来了的。
若是想要判断一个元素是否是在一个集合里,通常想到的是将全部元素保存起来,而后经过比较肯定。链表,树等等数据结构都是这种思路,可是随着集合中元素的增长,咱们须要的存储空间愈来愈大,检索速度也愈来愈慢(O(n),O(logn))。
不过世界上还有一种叫做散列表(又叫哈希表,Hash table)的数据结构(有一个动态数组+ 一个hash函数)。它能够经过一个Hash函数将一个元素映射成一个位阵列(Bit array)中的一个点。这样一来,咱们只要看看这个点是否是1就能够知道集合中有没有它了。这就是布隆过滤器的基本思想。
Hash面临的问题就是冲突。假设Hash函数是良好的,若是咱们的位阵列长度为m个点,那么若是咱们想将冲突率下降到例如 1%, 这个散列表就只能容纳m / 100个元素。显然这就不叫空间效率了(Space-efficient)了。解决方法也简单,就是使用多个Hash,若是它们有一个说元素不在集合中,那确定就不在。若是它们都说在,虽然也有必定可能性它们在说谎,不过直觉上判断这种事情的几率是比较低的。
Bloom Filter是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。Bloom Filter的这种高效是有必定代价的:在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误认为属于这个集合(false positive)。所以,Bloom Filter不适合那些“零错误”的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter经过极少的错误换取了存储空间的极大节省。
总结起来讲:
bloomfilter,布隆过滤器:迅速判断一个元素是否是在一个庞大的集合内,可是他有一个
弱点:它有必定的误判率误判率:本来不存在于该集合的元素,布隆过滤器有可能会判断说它存在,可是,若是布隆过滤器判断说某一个元素不存在该集合,那么该元素就必定不在该集合内。
python
2、布隆过滤器的优缺点
一、优势
相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优点。
(1)布隆过滤器存储空间和插入/查询时间都是常数。
(2)另外, Hash函数相互之间没有关系,方便由硬件并行实现。
(3)布隆过滤器不须要存储元素自己,在某些对保密要求很是严格的场合有优点。
(4)布隆过滤器能够表示全集,其它任何数据结构都不能;
(5)k和m相同,使用同一组Hash函数的两个布隆过滤器的交并差运算可使用位操做行。
(6)能快速的判断元素存在不存在,远远的缩小存储数据的规模。
算法
二、缺点
可是布隆过滤器的缺点和优势同样明显。
(1)误算率是其中之一。随着存入的元素数量增长,误算率随之增长。可是若是元素数量太少,则使用散列表足矣。
(2)另外,通常状况下不能从布隆过滤器中删除元素。咱们很容易想到把位列阵变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就能够了。然而要保证安全的删除元素并不是如此简单。首先咱们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是没法保证的。
(3)另外计数器回绕也会形成问题。在下降误算率方面,有很多工做,使得出现了不少布隆过滤器的变种。
数据库
三、使用场景考量
(1)存在必定的误判率,那么在你不能容忍有错误率的状况,布隆过滤器不适用;
(2)布隆过滤器不支持删除操做
编程
3、实现原理
布隆过滤器须要的是一个位数组(跟位图(bitmap)相似, bytes数组)和K个映射函数(跟Hash表相似),在初始状态时,对于长度为m的位数组array,它的全部位被置0。
数组
一、布隆过滤器添加元素
(1)对于有n个元素的集合S={S1,S2...Sn},经过k个映射函数{f1,f2,......fk};
(2)将集合S中的每一个元素Sj(1<=j<=n)映射为k个值{g1,g2...gk},
(3)而后再将位数组array中相对应的array[g1],array[g2]......array[gk]置为1。
安全
二、布隆过滤器查询元素
(1)查询W元素是否存在集合中的时候,将W经过哈希映射函数{f1,f2,......fk},获得集合g
(2)获得集合g的K个值{g1,g2...gk},对应位数组上的k个点。
(3)若是k个点的其中有一个点不为1,则能够判断该元素必定不存在集合中。反之,若是k个点都为1,则该元素可能存在集合中。
注意:此处不能判断该元素是否必定存在集合中,可能存在必定的误判率。能够从图中能够看到:假设某个元素经过映射对应下标为4,5,6这3个点。虽然这3个点都为1,可是很明显这3个点是不一样元素通过哈希获得的位置,所以这种状况说明元素虽然不在集合中,也可能对应的都是1,这是误判率存在的缘由。
网络
三、自定义一个布隆过滤器的时候须要作的事情
(1)初始化一个位数组
(2)实现K个hash函数
(3)实现查询和插入操做
查询和插入操做须要作的事情:对插入进来的值进行hash计算,有几个hash函数,就计算几回,每次计算出来的结果值,都根据这个值,去位数组里面把相应位置的0变成1;
对查询操做来讲,只须要把你要查询的这个key值进行k个hash函数的调用,而后再判断计算出来的这个k个值对应的维数组上的值是否是有一个为0,若是有一个为0,那就表示,该key不在这个集合里面。
数据结构
4、哈希函数/哈希表
一、概念
哈希表中元素是由哈希函数肯定的。将数据元素的关键字K做为自变量,经过必定的函数关系(称为哈希函数),计算出的值,即为该元素的存储地址,也即一个元素在哈希表中的位置是由哈希函数决定的。dom
二、特色
(1)若是两个散列值是不相同的(根据同一函数),那么这两个散列值的原始输入也是不相同的。
(2)散列函数的输入和输出不是惟一对应关系的,若是两个散列值相同,两个输入值极可能是相同的。但也可能不一样,这种状况称为 “散列碰撞”(或者 “散列冲突”)。
ide
三、哈希构造方法
(1)直接定址法
取关键字或关键字的某个线性函数值为哈希地址。即H(key)=key 或 H(key)=akey+b (a,b为常数)。
(2)数字分析法
若关键字是以r为基的数(如:以10为基的十进制数),而且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
(3)平方取中法
取关键字平方后的中间几位为哈希地址,是比较经常使用的一种。
(4)折叠法
将关键字分割成位数相同的几部分(最后一部分的位数可不一样),而后取这几部分的叠加和(舍去进位)做为哈希地址。适用于关键字位数比较多,且关键字中每一位上数字分布大体均匀时。
(5)除留余数法
取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址(p为素数)
H(key)=key MOD p,p<=m (最简单,最经常使用)p的选取很重要
通常状况,p能够选取为质数或者不包含小于20的质因数的合数(合数指天然数中除了能被1和自己整除外,还能被其余数(0除外)整除的数)。
(6)随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址。即H(key)=rando(key),其中random为随机函数。适用于关键字长度不等时。
总结:实际工做中根据状况不一样选用的哈希函数不一样,一般,考虑因素以下:
(1)计算哈希函数所需时间(包括硬件指令的因素)
(2)关键字的长度
(3)哈希表的大小
(4)关键字的分布状况
(5)记录的查找频率
四、哈希碰撞
概念:即两个不一样的关键字,经过同一个哈希函数计算得出的结果值同样的。
五、解决哈希碰撞
(1)拉链法
拉出一个动态链表代替静态顺序存储结构,能够避免哈希函数的冲突,不过缺点就是链表的设计过于麻烦,增长了编程复杂度。此法能够彻底避免哈希函数的冲突。
(2)多哈希法
设计二种甚至多种哈希函数,能够避免冲突,可是冲突概率仍是有的,函数设计的越好或越多均可以将概率降到最低(除非人品太差,不然几乎不可能冲突)。
(3)开放地址法
开放地址法有一个公式:Hi=(H(key)+di) MOD m i=1,2,...,k(k<=m-1)
其中,m为哈希表的表长。di 是产生冲突的时候的增量序列。
若是di值可能为1,2,3,...m-1,称线性探测再散列。
若是di取1,则每次冲突以后,向后移动1个位置。
若是di取值可能为1,-1,4,-4,9,-9,16,-16,...kk,-kk(k<=m/2)称二次探测再散列。
若是di取值可能为伪随机数列,称伪随机探测再散列。
(4)建域法
假设哈希函数的值域为[0,m-1],则设向量HashTable[0..m-1]为基本表,另外设立存储空间向量OverTable[0..v]用以存储发生冲突的记录。
5、误判率估计
如今咱们了解了布隆过滤器的大体工做原理了,那咱们就来计算一下这个误判率。
数组的大小:m 总共的数据大小为:n hash函数的个数为:k
假设布隆过滤器中的hash function(哈希函数)知足simple uniform hashing(单一均匀散列)假设:每一个元素都等几率地hash到m个slot中的任何一个,与其它元素被hash到哪一个slot无关。若m为bit数,则:
对某一特定bit位,在一个元素调用了某个hash函数以后,被改为了1的几率是:
对某一特定bit位,在一个元素由某特定hash function插入时没有被置位为1的几率为:
则k个hash function中没有一个对其置位为1的几率,也就是该bit位在 k 次hash以后还一直保持为0的几率:
若是插入了n个元素,但都未将其置为1,也就是当全部的元素都被插入进来之后,某一个特定的bit位尚未被改为1的几率:
则此位置被置为1(被改为了1)的几率,也就是当全部的元素都被插入进来之后,某一个特定的bit位被改为1的几率:
如今检测某一元素是否在该集合中。代表某个元素是否在集合中所需的 k 个位置都按照如上的方法设置为 "1",可是该方法可能会使算法错误的认为某一本来不在集合中的元素却被检测为在该集合中(False Positives),即k个位置都是1的几率如下公式肯定:
其实上述结果是在假定由每一个 Hash 计算出须要设置的位(bit) 的位置是相互独立为前提计算出来的,不难看出,随着 m(位数组大小)的增长,假正例(False Positives)的几率会降低,同时随着插入元素个数 n 的增长,False Positives的几率又会上升,对于给定的m,n。
(1)如何选择Hash函数个数 k 由如下公式肯定:
推导过程:
由上面计算出的结果,如今计算对于给定的m和n,k为什么值时可使得误判率最低。设误判率为k的函数为:
翻译一下,也就是当m和n肯定了之后,咱们应该设置k为多少能使误判率最低呢?
当肯定了m和n以后,咱们要求出一个k使f(k)的值最小。
咱们能够肯定k,m,n三者之间的关系以后,咱们能够保证误判率最小
首先,设
则上面的式子化简为:
对两边都取对数,得出:
两边对k求导,得出:
接着,来求最值:
因此:
因此:
因此:
此时的误判率:
能够看出若要使得误判率≤1/2,则:
(2)而对于给定的False Positives几率 p,选择最优的位数组大小m的公式为:
上式代表,位数组的大小最好与插入元素的个数成线性关系,对于给定的 m,n,k,假正例几率最大为:
六、代码实现
(1)python代码实现
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]``` # 七、总结 在计算机科学中,咱们经常会碰到时间换空间或者空间换时间的状况,即为了达到某一个方面的最优而牺牲另外一个方面。Bloom Filter在时间空间这两个因素以外又引入了另外一个因素:错误率。在使用Bloom Filter判断一个元素是否属于某个集合时,会有必定的错误率。也就是说,有可能把不属于这个集合的元素误认为属于这个集合(False Positive),但不会把属于这个集合的元素误认为不属于这个集合(False Negative)。在增长了错误率这个因素以后,Bloom Filter经过容许少许的错误来节省大量的存储空间。 自从Burton Bloom在70年代提出Bloom Filter以后,Bloom Filter就被普遍用于拼写检查和数据库系统中。近一二十年,伴随着网络的普及和发展,Bloom Filter在网络领域得到了新生,各类Bloom Filter变种和新的应用不断出现。能够预见,随着网络应用的不断深刻,新的变种和应用将会继续出现,BloomFilter必将得到更大的发展。