556-大数据查重-布隆过滤器

本文详细介绍了布隆过滤器的工作原理及其在URL过滤、黑名单过滤等场景中的高效使用。布隆过滤器结合哈希表和位图算法,通过多个哈希函数减少内存占用,但可能导致误判。文章讨论了哈希函数的选择和误判率的数学分析,并提供了C++实现的布隆过滤器代码示例,展示了如何在实际应用中降低误判概率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在缓存服务器redis,在黑名单过滤,钓鱼网站过滤,URL过滤这些场景中,布隆过滤器很常见
布隆过滤器就是把哈希表方法和位图算法结合起来
在这里插入图片描述
在这里插入图片描述

布隆过滤器图解

在这里插入图片描述

比如说,我们有3个哈希函数,位数组的长度是7个
在这里插入图片描述
我们通过第一个哈希函数,我们把这个key所要存储的数据不管是整数,还是字符串,还是IP地址,还是URL,作为输入参数,通过哈希函数得到的位置,就是位图数组范围内的一个下标。
假设一个输入参数通过第一个哈希函数得出的结果是0
通过第二个哈希函数得出的结果是4
通过第三个哈希函数得出的结果是6

在这里插入图片描述
也就是说,当第一个数据通过3个哈希函数分别进行计算,映射得到的结果是0,4,6,我们会把位数组的0号元素,4号元素和6号元素都置为1
在这里插入图片描述
0,4,6这3个位从0置成了1
查询的时候我们只需要判断这3个位,如果这3个位都是1,则这个key存在,如果这3个位中有的位不是1,则表示这个key不存在。

同样的,我们处理第二个数据,分别通过3个哈希函数的值是2,4,5
在这里插入图片描述
我们需要把位数组的2号位置和4号位置和5号位置,这3个元素位置也置为1
查询的时候我们只需要判断这3个位,如果这3个位都是1,则这个key存在,如果这3个位中有的位不是1,则表示这个key不存在
在这里插入图片描述
在这里插入图片描述
布隆过滤器增加一个元素的步骤:
在这里插入图片描述
这个效率可是相当快的,因为这全部都是求一组哈希函数就OK了,都是O(1)的时间复杂度。
我们现在不需要像位图算法(1,3,10000000)那样按照最大值来开辟空间。
我们现在是把这些数据经过不同的哈希函数处理,让它的结果落在这7个位里面,得到0-6的序号,采用哈希的思想。我们可以把10000000映射到这7个位的某个位上。就避免了位图算法的缺陷。

问题1

在这里插入图片描述
答案是不能的
因为不同的key经过3组哈希函数得到的结果很有可能是相同的。因为只要有哈希函数, 肯定就会产生哈希冲突。也就是说,上图位图数组中4号位置的值是1,但是表示了2个key值的存在了,假如说要删除,经过k个哈希函数计算,得到原始的key在位图数组里都分别在哪几个位,然后把这几个位都置为0,但是很有可能多个key映射到相同的位上,如果删除就乱套了,不同key共用一个或者多个位。如果布隆过滤器提供删除操作,会导致删除1个key,其他key也被删除了,也查询不到了
所以布隆过滤器没有提供删除操作。
在这里插入图片描述

问题2

在这里插入图片描述
如果此时我又判断第3个数据。
在这里插入图片描述
对于这个元素,从来没有向位图数组中添加过,但是经过既定的K个哈希函数处理后,分别得出0,2,4,把位图数组上的0,2,4置为1
但是我们之前添加的2个元素后,位图数组上的 0,4,6,2,4,5 位置上都置为1了, 现在导致在查询这个key的时候,看成1个假象了:
0,2,4一看,都是1,就认为这个key存在了,可是实际上,在插入这个key之前,0,2,4上的元素值已经为1了
在这里插入图片描述
在这里插入图片描述
我们要选择合适的哈希函数,跟这个位图数组的大小,可以降低误判的概率
实际上,在数学公式上,有专门对布隆过滤器的误判进行数学公式的推导,怎么选择哈希函数,怎么定义位图的长度,可以有效的降低误判概率
但是它的效率是非常高,综合了哈希表,增删查快,而且用的是位图数组,省内存,而且不存在位图数组的缺陷,因为采用的是哈希的概念,把位图数组当做哈希表了
位图数组是判断不存在,判断存在的话可能存在误判

布隆过滤器总结

在这里插入图片描述

运用场景1

在这里插入图片描述

在这里插入图片描述

运用场景2

我们有一个客户端请求到后端服务,提交一个注册或者登录按钮。
后端服务器有一个I/O模块,就是网络层,专门接收用户的请求。
那么I/O层会把这个请求分发到相应的一些工作线程,到服务service层处理业务,涉及到数据的增删改查,它要进行查询数据的时候,查询数据的请求比修改数据的请求多很多,查数据的时候,首先从redis缓存层查,如果缓存层可用查到,就不用去后面的数据库查,缓存层如果能查到,就直接把数据从redis取出来,redis是nosql的数据库,存储的是key-value,和map表一样,然后返回到I/O层,然后返回到客户端。如果说在redis找不到,才去后面的数据库DB(mysql)查找,查询以后,为了下一次查比较快,会把这个数据缓存在redis上 ,然后返回给用户,下一次查这个数据的时候就可以从redis缓存查了。
在这里插入图片描述

在这里插入图片描述
mysql是磁盘I/O,性能的瓶颈很快到达。
service层在redis查询是用的key,key就是ID号之类的唯一的值。通过用户ID查询到一些用户关心的数据,如果在redis查不到key,就去mysql查询。

在这里插入图片描述

在这里插入图片描述

stringhash.h

#pragma once


/// @brief BKDR Hash Function  
/// @detail 本 算法由于在Brian Kernighan与Dennis Ritchie的《The C Programming Language》一书被展示而得 名,是一种简单快捷的hash算法,也是Java目前采用的字符串的Hash算法(累乘因子为31)。  
template<class T>
size_t BKDRHash(const T* str)
{
    register size_t hash = 0;
    while (size_t ch = (size_t)*str++)
    {
        hash = hash * 131 + ch;   // 也可以乘以31、131、1313、13131、131313..  
        // 有人说将乘法分解为位运算及加减法可以提高效率,如将上式表达为:hash = hash << 7 + hash << 1 + hash + ch;  
        // 但其实在Intel平台上,CPU内部对二者的处理效率都是差不多的,  
        // 我分别进行了100亿次的上述两种运算,发现二者时间差距基本为0(如果是Debug版,分解成位运算后的耗时还要高1/3);  
        // 在ARM这类RISC系统上没有测试过,由于ARM内部使用Booth's Algorithm来模拟32位整数乘法运算,它的效率与乘数有关:  
        // 当乘数8-31位都为1或0时,需要1个时钟周期  
        // 当乘数16-31位都为1或0时,需要2个时钟周期  
        // 当乘数24-31位都为1或0时,需要3个时钟周期  
        // 否则,需要4个时钟周期  
        // 因此,虽然我没有实际测试,但是我依然认为二者效率上差别不大          
    }
    return hash;
}
/// @brief SDBM Hash Function  
/// @detail 本算法是由于在开源项目SDBM(一种简单的数据库引擎)中被应用而得名,它与BKDRHash思想一致,只是种子不同而已。  
template<class T>
size_t SDBMHash(const T* str)
{
    register size_t hash = 0;
    while (size_t ch = (size_t)*str++)
    {
        hash = 65599 * hash + ch;
        //hash = (size_t)ch + (hash << 6) + (hash << 16) - hash;  
    }
    return hash;
}
/// @brief RS Hash Function  
/// @detail 因Robert Sedgwicks在其《Algorithms in C》一书中展示而得名。  
template<class T>
size_t RSHash(const T* str)
{
    register size_t hash = 0;
    size_t magic = 63689;
    while (size_t ch = (size_t)*str++)
    {
        hash = hash * magic + ch;
        magic *= 378551;
    }
    return hash;
}
/// @brief AP Hash Function  
/// @detail 由Arash Partow发明的一种hash算法。  
template<class T>
size_t APHash(const T* str)
{
    register size_t hash = 0;
    size_t ch;
    for (long i = 0; ch = (size_t)*str++; i++)
    {
        if ((i & 1) == 0)
        {
            hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
        }
        else
        {
            hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
        }
    }
    return hash;
}
/// @brief JS Hash Function  
/// 由Justin Sobel发明的一种hash算法。  
template<class T>
size_t JSHash(const T* str)
{
    if (!*str)        // 这是由本人添加,以保证空字符串返回哈希值0  
        return 0;
    register size_t hash = 1315423911;
    while (size_t ch = (size_t)*str++)
    {
        hash ^= ((hash << 5) + ch + (hash >> 2));
    }
    return hash;
}
/// @brief DEK Function  
/// @detail 本算法是由于Donald E. Knuth在《Art Of Computer Programming Volume 3》中展示而得名。  
template<class T>
size_t DEKHash(const T* str)
{
    if (!*str)        // 这是由本人添加,以保证空字符串返回哈希值0  
        return 0;
    register size_t hash = 1315423911;
    while (size_t ch = (size_t)*str++)
    {
        hash = ((hash << 5) ^ (hash >> 27)) ^ ch;
    }
    return hash;
}
/// @brief FNV Hash Function  
/// @detail Unix system系统中使用的一种著名hash算法,后来微软也在其hash_map中实现。  
template<class T>
size_t FNVHash(const T* str)
{
    if (!*str)   // 这是由本人添加,以保证空字符串返回哈希值0  
        return 0;
    register size_t hash = 2166136261;
    while (size_t ch = (size_t)*str++)
    {
        hash *= 16777619;
        hash ^= ch;
    }
    return hash;
}
/// @brief DJB Hash Function  
/// @detail 由Daniel J. Bernstein教授发明的一种hash算法。  
template<class T>
size_t DJBHash(const T* str)
{
    if (!*str)   // 这是由本人添加,以保证空字符串返回哈希值0  
        return 0;
    register size_t hash = 5381;
    while (size_t ch = (size_t)*str++)
    {
        hash += (hash << 5) + ch;
    }
    return hash;
}
/// @brief DJB Hash Function 2  
/// @detail 由Daniel J. Bernstein 发明的另一种hash算法。  
template<class T>
size_t DJB2Hash(const T* str)
{
    if (!*str)   // 这是由本人添加,以保证空字符串返回哈希值0  
        return 0;
    register size_t hash = 5381;
    while (size_t ch = (size_t)*str++)
    {
        hash = hash * 33 ^ ch;
    }
    return hash;
}
/// @brief PJW Hash Function  
/// @detail 本算法是基于AT&T贝尔实验室的Peter J. Weinberger的论文而发明的一种hash算法。  
template<class T>
size_t PJWHash(const T* str)
{
    static const size_t TotalBits = sizeof(size_t) * 8;
    static const size_t ThreeQuarters = (TotalBits * 3) / 4;
    static const size_t OneEighth = TotalBits / 8;
    static const size_t HighBits = ((size_t)-1) << (TotalBits - OneEighth);

    register size_t hash = 0;
    size_t magic = 0;
    while (size_t ch = (size_t)*str++)
    {
        hash = (hash << OneEighth) + ch;
        if ((magic = hash & HighBits) != 0)
        {
            hash = ((hash ^ (magic >> ThreeQuarters)) & (~HighBits));
        }
    }
    return hash;
}
/// @brief ELF Hash Function  
/// @detail 由于在Unix的Extended Library Function被附带而得名的一种hash算法,它其实就是PJW Hash的变形。  
template<class T>
size_t ELFHash(const T* str)
{
    static const size_t TotalBits = sizeof(size_t) * 8;
    static const size_t ThreeQuarters = (TotalBits * 3) / 4;
    static const size_t OneEighth = TotalBits / 8;
    static const size_t HighBits = ((size_t)-1) << (TotalBits - OneEighth);
    register size_t hash = 0;
    size_t magic = 0;
    while (size_t ch = (size_t)*str++)
    {
        hash = (hash << OneEighth) + ch;
        if ((magic = hash & HighBits) != 0)
        {
            hash ^= (magic >> ThreeQuarters);
            hash &= ~magic;
        }
    }
    return hash;
}

布隆过滤器代码应用

#include <iostream>
#include <vector>
#include "stringhash.h"
#include <string>
using namespace std;

//布隆过滤器实现
class BloomFilter
{
public:
    BloomFilter(int bitSize = 1471)//构造函数 
        : bitSize_(bitSize)
    {
        bitMap_.resize(bitSize_ / 32 + 1);//int类型数组的大小 
    }
    
public: 
    //添加元素 O(1)
    void setBit(const char* str)
    {
        //计算k组哈希函数的值
        int idx1 = BKDRHash(str) % bitSize_;
        int idx2 = RSHash(str) % bitSize_;
        int idx3 = APHash(str) % bitSize_;

        //把相应的idx1 idx2 idx3这几个位全部置1
        int index = 0;
        int offset = 0;

        index = idx1 / 32;
        offset = idx1 % 32;
        bitMap_[index] |= (1 << offset);

        index = idx2 / 32;
        offset = idx2 % 32;
        bitMap_[index] |= (1 << offset);

        index = idx3 / 32;
        offset = idx3 % 32;
        bitMap_[index] |= (1 << offset);
    }

    //查询元素 O(1)
    bool getBit(const char* str)
    {
        //计算k组哈希函数的值
        int idx1 = BKDRHash(str) % bitSize_;
        int idx2 = RSHash(str) % bitSize_;
        int idx3 = APHash(str) % bitSize_;

        int index = 0;
        int offset = 0;

        index = idx1 / 32;
        offset = idx1 % 32;
        if (0 == (bitMap_[index] & (1 << offset)))
        {
            return false;
        }

        index = idx2 / 32;
        offset = idx2 % 32;
        if (0 == (bitMap_[index] & (1 << offset)))
        {
            return false;
        }

        index = idx3 / 32;
        offset = idx3 % 32;
        if (0 == (bitMap_[index] & (1 << offset)))
        {
            return false;
        }

        return true;
    }

private:
    int bitSize_;//位图的长度
    vector<int> bitMap_;//位图数组
};

//URL黑名单
class BlackList
{
public:
    void add(string url)//添加 
    {
        blockList_.setBit(url.c_str());
    }
    bool query(string url)//查询 
    {
        return blockList_.getBit(url.c_str());
    }
private:
    BloomFilter blockList_;
};

int main() 
{
    BlackList list;
    list.add("http://www.baidu.com");
    list.add("http://www.360buy.com");
    list.add("http://www.tmall.com");
    list.add("http://www.tencent.com");

    string url = "http://www.alibaba.com";
    cout << list.query(url) << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值