Bloom Filter原理与实现

Bloom Filter 原理 及C++ 实现 布隆过滤器[1](Bloom Filter)是由布隆(Burton Howard Bloom)在1970年提出的。 它实际上是由一个很长的二进制向量和一系列随机映射函数组成,布隆过滤器可以用于检索一个元素是否在一个集合中。 它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率(假正例False positives,即Bloom Filter报告某一元素存在于某集合中,但是实 阅读详情

在做与Web爬虫有关的任务时,经常这样需要做这样的判断:对于新爬到的URL,我们需要判断这个新的URL是否已经在已有的URL集合中存在了。但是当已经存在的URL集合的数据量极其庞大时,这个存在性的判断工作就变得很有挑战性。

把这个问题抽象出来,就是说:现在需要一种算法(工具),帮助我们实现一种高效而准确的,元素在集合中的存在性判断。

为了解决上面说的这一类问题,人们从简入难,想出了很多办法:

  1. 将元素全部存在一个数据库中。判断存在性时,对整个数据库做线性扫描。显然,这种方法永远不会出错,但是时空效率都不高,时间上讲,线性时间复杂度;空间上讲,存储元素的数据库也相当大。

  2. 进一步,很容易想到可以用一些抗碰撞性较强的安全的哈希函数先对每个元素求取哈希值,在将这些哈希值存储到数据库中。如果单个元素的size比较大(至少是大于固定长度的哈希值),就能节省数据库本身的空间。这也是典型的以时间(增加了哈希值的计算)换取空间的做法。

  3. 紧接着,人们又在想,上面的方法当中对于查找的过程还都是O(n)的,有没有更快的,接近O(1)的方法。那么好了,散列表(哈希表)就派上了用场,用哈希值来决定元素存储的位置,很快就能查到那个位置有没有元素。当然,为了更节省空间,可以先求取哈希值,再用哈希值构成的集合做散列表。

  4. 其实3中已经是把某个元素映射到了某个位置上,只不过这个位置上保存的是元素本身或者哈希值,而这种关系完全可以只用计算机科学中最简洁的符号0,1来表示某个位置是否被映射了。

我们把最后这种方法叫做Bit-Map方法。今天要讲的Bloom Filter就是一中典型的Bit-Map方法。

Bloom Filter 的构造

上面的方法4,无论从时间,还是从空间的角度已经很优了。但是还有一个问题,那就是碰撞的问题,单个哈希函数计算得到哈希值,再把这个哈希值映射到一个二进制数组的某个位置上,就很有可能发生碰撞。但是,换个角度思考,如果我们能通过某种方式,极大程度上降低碰撞的概率,那么,即便依然还有小概率的碰撞可能,在工程实际中也是可以接受的。

误报率(False positive)

这里先补充一个概念:误报率(False positive),又叫假阳性

拿最开始讲的网页爬虫的问题来说,在建立BitMap时,如果发生碰撞,那我们就会认为新爬到的这个URL是已经存在于已有集合中了,而事实上,却是不存在的。这个意思抽象出来,就是把本来不存在的事物(False)误报为已存在事物(Positive)的错误率。在一般的应用场景中,有极小的误报率是可以被接受的。比如爬虫时,少爬几个网页并没有什么太大关系;医疗检查时,一个健康的人被医生误判我们患了某种疾病(False Positive),总比一个有病的人没有被检查出来(False Negative)要强得多。我们把后面一种情况称为假阴性,也就是“漏报”。

好,回归正题,通过上面的分析可知,碰撞在元素针对集合的存在性判断问题中,会导致误报率的发生,而误报率如果不大的话,对这个问题的影响也就不大。所以,当然可以设计一种方法,在降低碰撞概率的前提下,生成相应的bit串。

怎么降低碰撞概率呢?2个途径:

  1. 使用多个哈希,替代之前的单个哈希;
  2. 增大bit数组的长度。

基本结构

按照这2个思路,Bloom Filter应运而生。生成步骤如下:

  1. 选取 k 个哈希函数,记为{h1,h2,,hk}. 至于参数 k 的选择问题,我后面再说。

  2. 假设现在有n个元素需要被映射到bit数组中,bit数组的长度是 m . 初始时,将m位的bit数组的每个位置的元素都置为0。一样地,关于参数 m 的选择我之后说。

  3. 现在,把这个n个元素依次用第1步选取的 k 个哈希函数映射到bit数组的位置上,bit数组被映射到的位置的元素变为1。显然,一个元素能被映射到k个位置上。过程如图Fig.1所示,现在把元素集合 {x,y,z} 通过3个哈希函数映射到一个二进制数组中。

  4. 最后,需要检查一个元素是否在已有的集合中时,同样用这 k 个哈希函数把要判断的元素映射到bit数组的位置上,只要bit数组被映射到的位中有一个位不是1,那一定说明了这个元素不在已有的集合内。如图Fig.1所示,检查w是否在集合中时,有一个哈希函数将 w 映射到了bit数组的元素为0的位置。

这里写图片描述

上面用的这张图是我盗的,感谢https://my.oschina.net/kiwivip/blog/133498这篇文章的作者。

参数选择

这一部分是整个Bloom Filter构造的核心内容,因为参数的选择直接决定了误报率的大小,从而直接影响准确性。

我们选择合适参数的目的,就是要降低误报率,所以先看看在什么情况下误报率最低:

  1. 假设bit数组m长,那么任意一个元素,被任意一个哈希函数映射到某一位的概率是平均的,都是 1m ,同理,没有被映射到的概率是 11m

  2. 假设现在一共有 k 个不同的哈希函数,那么,bit数组的某一位能,经过这k个哈希函数还没有被这个元素映射到的概率是 (11m)k
  3. 假设一共有 n 个元素需要被插入bit数组,那么插入n个元素后,对于bit数组的某一位来说,依然为0的概率是 (11m)nk ,换句话说,某一位为1的概率是: 1(11m)nk
  4. 假设现在对某个特定的元素存在误报,也就是说,这个元素经过 k 个哈希函数映射到的bit数组的k个位都是1,这个概率是: [1(11m)nk]k ,换句话说,这个概率就是误报率。

如果你认可我以上的4点的话,那现在的目的就是要使得 [1(11m)nk]k 达到最小值。

把这个式子化简一下:

[1(11m)nk]k=[1(11m)mnkm]k=[1(1+1m)mnkm]k=[1enkm]k

由上面的公式可知,false positive =[1enkm]k . 令 p=enkm ,则

fp=[1p]k=ekln(1p)=emnln(p)ln(1p)

其中, fp 表示误报率。由e指数的性质知, mnln(p)ln(1p) 达到最小时, fp 最低,也就是说, p=enkm=12 时,误报率最低。而 p=enkm=(11m)nk=12 ,可见这种情况下,bit数组中任意一位为0的概率是1/2,那也就是说,有一半的位置还是0.

因此,可以得到以下这个定理:

定理1: nkm=ln(2) 时,误报率达到最低。最低误报率为 (12)k ,此时bit数组中有一半的位还是0。其中, n 为要插入的元素数,k为选择的不同的哈希函数的个数, m 为bit数组的长度。

下面来确定bit数组的长度。首先可以明确的一点是m长的bit数组目的是要存储 n 个元素,但是因为有误报率的存在,也就是说,实际上bit数组可以存储的元素数量比是一个比n略大的数。

假设全集中一共有 u 个元素,因为误报率的存在,导致除了正常的n个元素能被插入到bit数组中,还有 fp(un) 个元素会被误报。换句话说,现在 m 长的bit数组一共能容纳的元素数为:n+fp(un)。当然,误报率嘛,越小越好,所以,这里设置的 fp 只是说可以接受的最大的误报率。

那么,不难推理出,对于某一个确定的bit数组来说,它能表示的集合数为: Cnn+fp(un)

如果这个数组有 m 位,那么能表示的集合数为:2mCnn+fp(un)

再顺着这个思路往前走,全集一共有 u 个元素,那么大小为n的可能的集合数为: Cnu

所以,要让m位的bit数组能够表示所有n个元素的集合,必须有: 2mCnn+fp(un)>Cnu

把上面不等式化简,提出 m

2mCnn+fp(un)mmm>Cnu>log2(CnuCnn+fp(un))>log2(CnuCnfpu)>log2(CnuCnfpu)log2(fpn)=nlog2(fp1)

每个箭头的详细推理过程,我这里省略了。这么推下来,就可以得到本文涉及的第2个定理:

定理2: 想要Bloom Filter的误报率小于 fp ,则bit数组的长度 m 需满足下面的关系式:

m>nlog2(fp1)

由上面的定理1可知, k=mnln2 时,误报率最低,达到 (12)k ,而这个值,应该是要比定理2中的 fp 低的。如下:

(12)k=(12)mnln2fp

化简上面的式子:

mnlog2(fp1)ln2

也就是 m1.44nlog2(fp1) ,这个结果比之前定理2中计算的结果还要大1.44倍。

至此,好像Bloom Filter的几个参数都是相互影响的,那到底该如何在算法中设计呢?总结一下上面的内容,可以得到一个定理3.

定理3: 当需要设计一个误报率不超过 fp 的,容纳元素个数为 n 的Bloom Filter时,我们要求bit数组的长度m1.44nlog2(fp1),且哈希函数族中哈希函数的个数 k=ln2mn

这样,其实我们做出来的Bloom Filter的误报率会比要求的还要低。

Bloom Filter 的Python 实现

明确了Bloom Filter的参数选择问题,下面的就很简单了,一点点实现就行。我将给出Python代码,供大家参考。

参数生成

先确定参数:

import math


def gen_m(n, falsePositive):
    """m is the length of bit array"""

    return int(1.44 * n * (math.log(1 / falsePositive, 2)))


def gen_k(m, n):
    """k is the number of hash function in hash family"""

    return int(math.log(2, math.e) * (m / n))

怎么确定的上面用了相当大的篇幅讲,不再赘述。

哈希函数族生成

接下来是生成哈希函数族:这里我选用的是传统的MD5算法,为了能生成多个不同的哈希函数,我为每个MD5函数设置了一个种子,简单起见,比如说需要 k 个哈希函数,就选取区间[0,k1]的整数放置在哈希函数要映射的值前面。

比如现在需要对消息m映射,那么, k 个哈希函数的计算形式是:

r_1 = md5("0" + "m")
r_2 = md5("1" + "m")
...
r_k = md5("k-1" + "m")

当然可以有其他形式,值需要保证k个不同的哈希就行。

代码如下:

import hashlib


def genHashFamily(k):
    """generate k hash functions as hash family"""

    result = [hashlib.md5() for i in range(k)]

    for j in range(k):
        result[j].update(str(j).encode())

    return result

插入元素

现在将集合中的元素依次插入bit数组,直接给出代码:

def genBitArray(hashFamily, wordSet, m):
    """map the hash value of each word in word set to bit array according to hash family"""

    result = [0 for i in range(m)]

    for word in wordSet:
        for hashFunc in hashFamily:
            temp = hashFunc.copy()
            temp.update(word.encode())
            result[int(temp.hexdigest(), 16) % m] = 1

    return result

需要注意的两点:

  1. 用哈希函数的时候,一定要先复制一下这个md5对象,否则会影响哈希函数族,这也是Python中md5对象使用的特性

  2. 我们是通过先将16进制的哈希值转换为整数,再用整数对 m 取余实现元素到位置的映射的。

检测函数

对于一个新的元素,怎样判断是否属于某个集合呢?既然这个集合已经全部映射到了一个Bloom Filter当中,我们就按照同样的思路映射这个新的元素,看看它被映射到的位置是否都是1,只要有一个位是0,就判为false. 函数如下:

def checkWord(word, hashFamily, bitArray):
    """check if the word can be map in bit array correctly with hash family"""

    m = len(bitArray)

    for hashFunc in hashFamily:

        temp = hashFunc.copy()
        temp.update(word.encode())
        if bitArray[int(temp.hexdigest(), 16) % m] != 1:
            return False
    return True

以上,我只是将各个关键的函数向大家展示一下,至于这个项目的完整代码,请参考我的github主页:https://github.com/guoziqingbupt/Bloom-Filter

Bloom Filter: 概念和实现原理(Java) 当一个元素被添加到集合中时,通过多个哈希函数将该元素映射到位数组的不同位置,并将这些位置的位设置为1。当要查询一个元素是否存在于集合中时,同样使用这些哈希函数将元素映射到位数组的位置,如果所有位置的位都为1,则说明该元素可能存在于集合中;如果任何一个位置的位为0,则说明该元素一定不存在于集合中。当判断一个元素不存在于集合中时,可能存在误报的情况,但当判断一个元素存在于集合中时,一定是准确的。查询元素:将要查询的元素通过多个哈希函数映射到位数组的位置,如果所有位置的位都为1,则说明该元素可能存在于集合中; 阅读详情

相关推荐

bloomfilter:简单的bloom过滤器实现

数据结构课设 BloomFilter(报告)

合工大 数据结构 课程设计 布隆过滤器

ajajajsj的博客 686

Bloom过滤器的C++实现

Bloom Filter原理C++实现,并利用Bloom Filter实现简单的词典,进行字词查询

布隆过滤器(BloomFilter)原理 实现和性能测试

布隆过滤器是一种大家在学校没怎么学过,但在计算机很多领域非常常用的数据结构,它可以用来高效判断某个key是否属于一个集合,有极高的插入和查询效率(O(1)),也非常省存储空间。当然它也不是完美无缺,它也有自己的缺点,接下来跟随我一起详细了解下BloomFilter实现原理,以及它优缺点、应用场景,最后再看下Google guava包中BloomFilter实现,并对比下它和HashSet在不同...

xindoo 9458

BloomFilter算法概述

Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法。通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合。一. 实例 为了说明Bloom Filter存在的重要意义,举一个实例: 假设要你写一个网络蜘蛛(web crawler)。由于网络间的链接错综复杂,蜘蛛...

weixin_34082695的博客 707

大规模数据处理Bloom Filter C++代码实现

Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法。通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合。 一. 实例    为了说明Bloom Filter存在的重要意义,举一个实例:   假设要你写一个网络蜘蛛(web crawler)。由于网络间的链接错综复杂,蜘蛛在网络间爬行很可能会形成“环”。为了避免形成“环”,就

Linux平台软件设计与开发 4227

No.19 Counting Bloom Filter原理实现

0x00 前言标准的 Bloom Filter 是一种比较简单的数据结构,只支持插入和查找两种操作。在所要表达的集合是静态集合的时候,标准 Bloom Filter 可以很好地工作,但是如果要表达的集合经常变动,标准Bloom Filter的弊端就显现出来了,因为它不支持删除操作。这就引出来了本文要谈的 Counting Bloom Filter,后文简写为 CBF。0x01 原理一、BF 为什么不

木东居士 1851

Bloom Filter原理实现

Bloom Filter:是一个比特数组,表示具有一定误报率的集合。主要优势在于其大小(比特位个数)为常数且在初始化时被设置,增加更多的元素到一个Bloom Filter 中不会增加它的大小,仅增加误报的概率。一般包含两个方法:add(),contains()。 误报率: r = (1-exp(-kn/m))k    ,k = ln(2) * (m/n)    , r = 0.6185*(m/n

三分地 3392

Bloom Filter原理及python实现

文章目录一、Bloom Filter存在的意义二、Bloom Filter算法原理三、Bloom Filter的优化哈希函数选择参数设计误判率P(true)Hash Function的数目 kBitSet数组的大小 m四、python代码实现总结 一、Bloom Filter存在的意义 为了说明Bloom Filter存在的重要意义,举一个例子,也是我学习Bloom Filter的原因: 假如我们要写一个爬取微博全站信息的爬虫,由于网络中的链接关系错综复杂,爬虫在微博网站爬取信息的时候肯定会抓取到已经采集过

m0_51370181的博客 2497

Bloom Filter 原理实现

题外话:     很久没写博客了,因为前一段时间过年在家放假,又因为自己保研了,所以一直比较闲。整个假期,基本都在准备毕业设计的相关内容。我毕业设计的方向是关于搜索引擎的,因此,期间阅读了大量相关论文。阅读了很多论文和技术书籍之后,我有几点感触。首先,发现国内很多论文或是书籍只是大量引述其他人的研究结果,自己的独特的见解非常少,一篇文章,70%的内容都是在以介绍为主,感觉发这样的论文是没有什么意...

weixin_34026484的博客 210

open bloom filter

open bloom filter是我见过的bloom filter之中写的比较好的一个,但是其中的数值范围过大,获取hash函数时测试次数过多。希望对你有所裨益,使用的时候请遵守原作者的权益。见过csdn上好多不要脸的人,别人写的东西,自己上传一下,还要那么多资源分,谴责一下。

布隆过滤器(Bloom Filter)原理分析及代码实现

布隆过滤器(Bloom Filter)原理分析及代码实现 直观的说,bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中。 和一般的hash set不同的是,这个算法无需存储key的值,对于每个key,只需要k个比特位,每个存储一个标志,用来判断key是否在集合中。 算法: 1. 首先需要k个hash函数,每个函数可以把key散列成为1个整数 2. 初始化时,需要一个长度为n比特的数组,每个比特位初始化为0 3. 某个key加入集合时,用k个hash函数计算出k个散列值,并

jenie的专栏 1302

Bloom Filter原理实现

Bloom Filter Scrapy-Redis的存储 Bloom Filter算法 散列算法 多个散列函数 insert方法 exists方法 测试实例 setbit和getbit的用法 误区 总结 Bloom Filter Scrapy-Redis的存储 Scrapy-Redis将Request的指纹存储到Redis集合中,存储为长度为40,每一位都是16进制。 ...

kong 7319

Counting Bloom Filter原理实现

0x00 前言 标准的 Bloom Filter 是一种比较简单的数据结构,只支持插入和查找两种操作。在所要表达的集合是静态集合的时候,标准 Bloom Filter 可以很好地工作,但是如果要表达的集合经常变动,标准Bloom Filter的弊端就显现出来了,因为它不支持删除操作。这就引出来了本文要谈的 Counting Bloom Filter,后文简写为 CBF。 0x01 原理 一、BF 为什么不支持删除 BF 为什么不能删除元素?我们可以举一个例子来说明。 比如要删除集合中的成员 dan

meser88的博客 583

Bloom Filter概念和实现原理

Bloom Filter是由Howard Bloom在1970年提出的二进制向量数据结构,它具有较好的时间和空间效率,用来检测一个元素是否在某个集合中,但是缺点是,有一定的错误率和删除困难。

胥平勇的博客 717
上一篇: 赫夫曼编码原理与实现
下一篇: lintcode - 中序遍历和后序遍历树构造二叉树
guoziqing506
guoziqing506 领域专家: 算法与数据结构技术领域 领域专家: 算法与数据结构技术领域
博客等级 码龄10年 1006粉丝 239原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值