布隆过滤器(BloomFilter)的实现

布隆过滤器 – 空间效率很高的数据结构

上面这个链接对布隆过滤器进行了比较详细的介绍,可以仔细看一看。在这里,我自己主要写一写自己的理解,并用代码实现一个简单的版本。

BloomFilter往往用于数据量太大内存一下子存不了的情况,其实本质有点类似bit-map 的扩展,它的原理:

当一个元素被加入集合时,通过 K 个 Hash 函数将这个元素映射成一个位阵列(Bit array)中的 K 个点,把它们置为 1。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:

如果这些点有任何一个 0,则被检索元素一定不在;
如果都是 1,则被检索元素很可能在。

我们举一个例子,比如当我们想要爬虫一个很大的网页,每个网页又包含了很多很多网页,我们需要爬取这上面的信息。为了防止重复爬取某些网页,我们需要把已爬取的网页存在一个集合中,每次遇到一个网页和这个集合比较确定是否要爬取。

网页url(如www.baidu.com)可以看作是一个比较长的字符串string,如果直接保存string显然太浪费空间了。

为此我们可以利用哈希函数,将每个url通过哈希映射为一个值,将这个值保存起来,我们只需要到时候看这个值在不在就可以判定这个url是否存在。由于只需要知道在或者不在这两种状态,使用位图能够极大的帮我们节省空间。

优点:

根据上面所说,布隆过滤器的查询效率和空间效率会很高,这是因为它结合了位图和哈希函数的优点。

缺点:

俗话说,人无完人,布隆过滤器的优点很棒,但是它也有着它的缺陷,那就是哈希函数自带的哈希冲突。我们知道没有任何哈希函数能够避免冲突,布隆也是一样。

以上面的url为例,我们首先需要将一个url映射为一个整数n,(url)往往都是很长的字符串,根据常规的除留余数法,根据n % k将对应的bit位置1,表示这个字符串存在。在这里,有一个问题,字符串很长的情况下以及字符串很多的情况,必然会出现n % k的值重复,也就是说假如有两个string a,string b通过上面计算都将n % k对应的比特位置1,那么就无法判定某个字符串是否真的存在了。

这也就是布隆过滤器的一个缺点:误判。在上面的例子中,假设a已经将对应的bit位置1,此时就算b没有出现,但是由于a,b对应的比特位都一样是1,会误以为b存在,这就是所谓的误判。

为了减小误判率,我们可以设置多个hash函数,比如让一个string a映射到多个bit位上,只有当这些bit位都为1才能判定这个字符串a存在。这样以来误判率就减小了很多,虽然还是有可能误判。比如

这里写图片描述

如上图,由于之前a,b映射已经让c对应的600/1000位都为1了,那么就算c不存在,我们也会根据比特位来判定它存在,因此判定存在是不准确的(当然多个hash让这个误判几率变的很小)。但是如果判定不存在是准确的,因此只有当一个string通过hash映射的所有比特位为1才能判定存在性。这也就是之前的布隆原理。

当一个元素被加入集合时,通过 K 个 Hash 函数将这个元素映射成一个位阵列(Bit array)中的 K 个点,把它们置为 1。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:

如果这些点有任何一个 0,则被检索元素一定不在;
如果都是 1,则被检索元素很可能在。

下面我们给出它的实现代码:

它的底层是一个bitmap:(关于bitmap可见我的另一篇博客BitMap

#include "BitMap.h"
#include "HashFun.h"

// simple BloomFilter for string

class BloomFilter
{
public:
    BloomFilter(size_t num) //the num of string to be inserted
        : _bm(num * 5 * 2) // each string has five hashfun corresponding to 5 bit
        , _size(num * 5 * 2) 
    {}

    void Insert(const string& key)
    {
        size_t hash1 = HashFun1()(key) % _size;
        size_t hash2 = HashFun2()(key) % _size;
        size_t hash3 = HashFun3()(key) % _size;
        size_t hash4 = HashFun4()(key) % _size;
        size_t hash5 = HashFun5()(key) % _size;
        _bm.Set(hash1);
        _bm.Set(hash2);
        _bm.Set(hash3);
        _bm.Set(hash4);
        _bm.Set(hash5);
    }

    bool HasExisted(const string& key)
    {
        size_t hash1 = HashFun1()(key) % _size;
        if (!_bm.HasExisted(hash1))
            return false;
        size_t hash2 = HashFun2()(key) % _size;
        if (!_bm.HasExisted(hash2))
            return false;
        size_t hash3 = HashFun3()(key) % _size;
        if (!_bm.HasExisted(hash3))
            return false;
        size_t hash4 = HashFun4()(key) % _size;
        if (!_bm.HasExisted(hash4))
            return false;
        size_t hash5 = HashFun5()(key) % _size;
        if (!_bm.HasExisted(hash5))
            return false;
        return true;
    }
private:
    BitMap _bm;
    size_t _size;
};
  1. 这是一个简单版的布隆过滤器,针对字符串进行处理,当然布隆过滤器往往处理都是字符串,比如url,字典某个单词存在性等等,它包涵了两个头文件,一个是BitMap的实现,一个各种字符串哈希算法的实现,在我的github有完整的代码实现。
  2. 在构造函数中,num表示字符串的总数,为什么还要*5*2,5是hash函数的个数,2是增大空间,总之还是为了减小误判概率。
  3. Insert为插入元素,只需要将5个hash函数对应的比特位置1就好了,HasExisted是判断某个元素是否存在,存在返回true。
  4. 基本的布隆过滤器不支持删除操作,但是我们可以在原来的位数组中的每一位由 bit 扩展为 n-bit 计数器,实际上,基本的布隆过滤器可以看作是只有一位的计数器。我们将原来的插入操作扩展为n-bit 的位计数器加1,查找操作即检查位数组非零即可,而删除操作定义为把位数组的相应位减1,然而该方法也有位的算术溢出问题,即某一位在多次删除操作后可能变成负值,所以位数组大小 m 需要充分大,但是这样一来又增大它所占的内存。。。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值