当前位置: 首页 > news >正文

哈希扩展 --- 布隆过滤器

什么是布隆过滤器 

对于上一篇提到的位图,他是有一个比较严重的缺陷的,就是只适用于整型。哈希表/红黑树啥事都能搞,但是空间消耗很多,数据量很多的时候,红黑树就用不上了。所以如果是整型的话,就可以使用位图来解决,但是!不是位图呢???字符串?结构对象?这时候,位图就使不上劲了。

我们其实可以对位图进行变形,这就是布隆这个大佬提出来的这一种概念:

布隆过滤器(Bloom Filter)由 Burton Howard Bloom 于 1970 年提出,是一种紧凑且巧妙的概率型数据结构,专门用于在“海量非整型数据”中高效地判断“某元素一定不存在,或可能存在”。当数据量极大、元素本身不是整数、且内存无法容纳完整哈希表或红黑树时,传统位图无法直接使用,布隆过滤器便成为理想选择。

假设是字符串,就是通过某种哈希算法,将其转化成一个整型值,然后再通过这个整型值取映射到位图上!

但是则时候主要的问题就是哈希冲突,a和b字符串通过哈希算法出来的整型是不同的,但是来了一个c字符串,通过哈希算法转成的整型值是和a哈希算法后的整型值一致的!这就是哈希冲突!这时候也就有误判问题:就是通过位图知道a和b和c都在,但是只有a和b在,c不在的!所以我们判断一个 key 值在不在是不准确的,但是判断一个 key 值不在是准确的! 

那布隆是怎么考量这个问题的呢? 

它的核心思路分两步:

  1. 把任意类型的 key 通过 k 个独立哈希函数映射成 k 个整数;

  2. 将这 k 个整数对应到一张位图中的 k 个比特位并置 1。

插入时,重复上述两步即可完成;查询时,如果这 k 个比特位中有任意一位是 0,便可断言该 key 一定不存在;若全部为 1,则只能说 key 可能存在——存在误判概率(减少冲突,没有解决冲突)。由于布隆过滤器不保存原始值,只标记位,因此无法像哈希表那样解决冲突,而是通过增加哈希函数数量 k 来尽可能降低冲突率。这样,它在保证极高查询效率的同时,也节省了大量内存空间。

猪八戒是在的,但是孙悟空是不在的,但凡有一个位置不在,那么他就是不在! 

关于哈希函数要有几个?位图开多大比较合适?

哈希函数肯定不是越多越好的,太多了的话,就会消耗过多的空间,而且大多是1的冲突的概率也会是直线的上升的。下面其实有一个推导:

布隆过滤器误判率推导


推导过程:

说明:这个比较复杂,涉及概率论、极限、对数运算、求导函数等知识.

假设:

  • m:布隆过滤器的 bit 长度。

  • n:插入过滤器的元素个数。

  • k:哈希函数的个数。

布隆过滤器哈希函数等条件下某个位设置为 1 的概率:\frac{1}{m}

布隆过滤器哈希函数等条件下某个位设置不为 1 的概率:1-\frac{1}{m}

在经过 k 次哈希后,某个位置依旧不为 1 的概率:(1-\frac{1}{m})^k

根据极限公式:

\lim_{m \to \infty} \left(1 - \frac{1}{m}\right)^{-m} = e

\left(1 - \frac{1}{m}\right)^k = \left(\left(1 - \frac{1}{m}\right)^m\right)^{\frac{k}{m}} \approx e^{-\frac{k}{m}}

添加 n 个元素某个位置不置为 1 的概率:

\left(1 - \frac{1}{m}\right)^{kn} \approx {e^{-\frac{kn}{m}}}

添加 n 个元素某个位置置为 1 的概率:

1 - \left(1 - \frac{1}{m}\right)^{kn} \approx 1 - e^{-\frac{kn}{m}}

查询一个元素,k 次 hash 后误判的概率为都命中 1 的概率:

\left(1 - \left(1 - \frac{1}{m}\right)^{kn}\right)^k \approx \left(1 - e^{-\frac{kn}{m}}\right)^k

结论:

布隆过滤器的误判率为:

f(k) = \left(1 - e^{-\frac{kn}{m}}\right)^k = \left(1 - \frac{1}{e^{\frac{kn}{m}}}\right)^k

由误判率公式可知,在 k (哈希函数)一定的情况下,当 n(插入数据) 增加时,误判率增加,m(布隆过滤器的长度) 增加时,误判率减少。

mn 一定,在对误判率公式求导,误判率尽可能小的情况下,可以得到 hash 数个数:

k = \frac{m}{n}ln2 时误判率最低。

期望的误判率 p 和插入数据个数 n 确定的情况下,再把上面的公式带入误判率公式可以得到,通过期望的误判率和插入数据个数 n 得到 bit 长度:

m = -\frac{n \cdot \ln p}{(\ln 2)^2}  


布隆过滤器代码实现

各种字符串Hash函数 - clq - 博客园https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html

下面代码是一个 C++ 实现的布隆过滤器(Bloom Filter)模板类,它使用了三种不同的哈希函数:BKDR、AP 和 DJB。这个布隆过滤器类允许您设置键(插入元素)并测试键(检查元素是否可能存在于集合中)。此外,还提供了一个计算误判概率的方法。

代码中定义了三个哈希函数结构体 HashFuncBKDRHashFuncAPHashFuncDJB,每个结构体都重载了 operator() 来实现特定的哈希算法。

布隆过滤器类 BloomFilter 模板参数包括:

  • N:预计插入的数据个数。

  • X:每个元素使用的哈希函数个数,默认为 5。

  • K:键的类型,默认为 std::string

  • Hash1Hash2Hash3:三个哈希函数类型,默认分别为 HashFuncBKDRHashFuncAPHashFuncDJB

类中的方法包括:

  • Set(const K& key):将键插入布隆过滤器。

  • Test(const K& key):测试键是否可能存在于布隆过滤器中。

  • getFalseProbability():计算并返回理论误判率。

TestBloomFilter1 函数中,创建了一个布隆过滤器实例并插入了一些字符串,然后测试了这些字符串以及一些不在集合中的字符串。

TestBloomFilter2 函数中,进行了更大规模的测试,包括相似字符串和不相似字符串的误判率测试,并与理论误判率进行了比较。

#pragma once
#include<string>
#include"BitSet.h"// BKDR Hash Function 结构体定义
struct HashFuncBKDR
{// BKDR 哈希算法,由 Brian Kernighan 和 Dennis Ritchie 提出// 该算法在《The C Programming Language》一书中展示,Java 字符串哈希也采用此算法size_t operator()(const std::string& s){size_t hash = 0;for (auto ch : s){hash *= 31;hash += ch;}return hash;}
};// AP Hash Function 结构体定义
struct HashFuncAP
{// AP 哈希算法,由 Arash Partow 发明size_t operator()(const std::string& s){size_t hash = 0;for (size_t i = 0; i < s.size(); i++){if ((i & 1) == 0) // 偶数位字符{hash ^= ((hash << 7) ^ (s[i]) ^ (hash >> 3));}else              // 奇数位字符{hash ^= (~((hash << 11) ^ (s[i]) ^ (hash >> 5)));}}return hash;}
};// DJB Hash Function 结构体定义
struct HashFuncDJB
{// DJB 哈希算法,由 Daniel J. Bernstein 教授发明size_t operator()(const std::string& s){size_t hash = 5381;for (auto ch : s){hash = hash * 33 ^ ch;}return hash;}
};// BloomFilter 类模板定义
template<size_t N,size_t X = 5,class K = std::string,class Hash1 = HashFuncBKDR,class Hash2 = HashFuncAP,class Hash3 = HashFuncDJB>
class BloomFilter
{
public:// 将 key 插入布隆过滤器void Set(const K& key){size_t hash1 = Hash1()(key) % M;size_t hash2 = Hash2()(key) % M;size_t hash3 = Hash3()(key) % M;_bs.set(hash1);_bs.set(hash2);_bs.set(hash3);}// 测试 key 是否可能存在于布隆过滤器中bool Test(const K& key){size_t hash1 = Hash1()(key) % M;if (!_bs.test(hash1)){return false;}size_t hash2 = Hash2()(key) % M;if (!_bs.test(hash2)){return false;}size_t hash3 = Hash3()(key) % M;if (!_bs.test(hash3)){return false;}return true; // 可能存在误判}// 获取公式计算出的误判率double getFalseProbability(){double p = pow((1.0 - pow(2.71, -3.0 / X)), 3.0);return p;}private:// 位图大小,根据预计插入的数据个数 N 和每个元素使用的哈希函数个数 X 计算得出static const size_t M = N * X;// 使用 BitSet 库来实现位图bit::bitset<M> _bs;
};// 测试 BloomFilter 类的函数
void TestBloomFilter1()
{BloomFilter<10> bf;bf.Set("猪八戒");bf.Set("孙悟空");bf.Set("唐僧");std::cout << bf.Test("猪八戒") << std::endl;std::cout << bf.Test("孙悟空") << std::endl;std::cout << bf.Test("唐僧") << std::endl;std::cout << bf.Test("沙僧") << std::endl;std::cout << bf.Test("猪八戒1") << std::endl;std::cout << bf.Test("猪戒八") << std::endl;
}// 另一个测试 BloomFilter 类的函数
void TestBloomFilter2()
{srand(time(0));const size_t N = 1000000;BloomFilter<N> bf;std::vector<std::string> v1;std::string url = "猪八戒";for (size_t i = 0; i < N; ++i){v1.push_back(url + std::to_string(i));}for (auto& str : v1){bf.Set(str);}// v2 跟 v1 是相似字符串集(前缀一样),但是后缀不一样v1.clear();for (size_t i = 0; i < N; ++i){std::string urlstr = url;urlstr += std::to_string(9999999 + i);v1.push_back(urlstr);}size_t n2 = 0;for (auto& str : v1){if (bf.Test(str)) // 误判{++n2;}}std::cout << "相似字符串误判率:" << (double)n2 / (double)N << std::endl;// 不相似字符串集 前缀后缀都不一样v1.clear();for (size_t i = 0; i < N; ++i){std::string urlstr = "孙悟空";urlstr += std::to_string(i + rand());v1.push_back(urlstr);}size_t n3 = 0;for (auto& str : v1){if (bf.Test(str)){++n3;}}std::cout << "不相似字符串误判率:" << (double)n3 / (double)N << std::endl;std::cout << "公式计算出的误判率:" << bf.getFalseProbability() << std::endl;
}
PS D:\2024C语言\C++加餐\c-additional-meal\哈希扩展学习加餐> ./test
相似字符串误判率:0.127968
不相似字符串误判率:0.104435
公式计算出的误判率:0.091236

当然了,我们这里没有动态的去映射,后续我们可以更好的完善这个布隆过滤器! 

布隆过滤器删除问题

布隆过滤器默认是不支持删除的,因为例如“猪八戒”和“孙悟空”都映射在布隆过滤器中,他们映射的位有一个位是共同映射的(冲突的),如果我们把孙悟空删掉,那么再去查找“猪八戒”会查找不到,因为那么“猪八戒”间接被删掉了。

解决方案:可以考虑引用计数/计数标记的方式,一个位置用多个位标记,记录映射这个位的计数值,删除时,仅仅减减计数,那么就可以某种程度支持删除。但是这个方案也有缺陷,如果一个值不在布隆过滤器中,我们去删除,减减了映射位的计数,那么会影响已存在的值,也就是说,一个确定存在的值,可能会变成不存在,这里就很坑,因为在极端情况下,我们是使用了Test去判断在不在,但是可能会误判啊!!!当然也有人提出,我们可以考虑计数方式支持删除,但是定期重建一下布隆过滤器,这样也是一种思路。

布隆过滤器的应用

首先我们分析一下布隆过滤器的优缺点:

优点:

  • 效率高,节省空间,相比位图,可以适用于各种类型的标记过滤。

缺点:

  • 存在误判(在是不准确的,不在是准确的)。

  • 不好支持删除。

布隆过滤器在实际中的一些应用:

  • 爬虫系统中URL去重: 在爬虫系统中,为了避免重复爬取相同的URL,可以使用布隆过滤器来进行URL去重。爬取到的URL可以通过布隆过滤器进行判断,已经存在的URL则可以直接忽略,避免重复的网络请求和数据处理。(因为有时候会有环形网站的存在,可能存在死循环)

  • 垃圾邮件过滤: 在垃圾邮件过滤系统中,布隆过滤器可以用来判断邮件是否是垃圾邮件。系统可以将已知的垃圾邮件的特征信息存储在布隆过滤器中,当新的邮件到达时,可以通过布隆过滤器快速判断是否为垃圾邮件,从而提高过滤的效率。

  • 预防缓存穿透: 在分布式缓存系统中,布隆过滤器可以用来解决缓存穿透的问题。缓存穿透是指恶意用户请求一个不存在的数据,导致请求直接访问数据库,造成数据库压力过大。布隆过滤器可以先判断请求的数据是否存在于布隆过滤器中,如果不存在,直接返回不存在,避免对数据库的无效查询。类似redis就是缓存!对热数据的中间层的缓存!

  • 对数据库查询提效: 在数据库中,布隆过滤器可以用来加速查询操作。例如:一个app要快速判断一个电话号码是否注册过,可以使用布隆过滤器来判断一个用户电话号码是否存在于表中,如果不存在,可以直接返回不存在,避免对数据库进行无用的查询操作。如果在,再去数据库查询进行二次确认。

布隆过滤器其实应用听广泛的!!!


文章转载自:
http://bellhanger.dmyyro.cn
http://abortionist.dmyyro.cn
http://buddhistic.dmyyro.cn
http://accordion.dmyyro.cn
http://cdsl.dmyyro.cn
http://chondrification.dmyyro.cn
http://bombardment.dmyyro.cn
http://catenaccio.dmyyro.cn
http://allheal.dmyyro.cn
http://absorbate.dmyyro.cn
http://afrikaner.dmyyro.cn
http://aliturgical.dmyyro.cn
http://aloysius.dmyyro.cn
http://achinese.dmyyro.cn
http://asne.dmyyro.cn
http://anuretic.dmyyro.cn
http://americanise.dmyyro.cn
http://aegeus.dmyyro.cn
http://atmological.dmyyro.cn
http://chelation.dmyyro.cn
http://achromatopsy.dmyyro.cn
http://castries.dmyyro.cn
http://bludger.dmyyro.cn
http://abnegation.dmyyro.cn
http://byo.dmyyro.cn
http://bookman.dmyyro.cn
http://alpeen.dmyyro.cn
http://bar.dmyyro.cn
http://anemochory.dmyyro.cn
http://ameba.dmyyro.cn
http://www.dtcms.com/a/277817.html

相关文章:

  • 肿瘤浸润淋巴细胞是什么,与三级淋巴结构的关系
  • 会计 - 22 - 外币折算
  • Linux713 SAMBA;磁盘管理:手动挂载,开机自动挂载,自动挂载
  • 补:《每日AI-人工智能-编程日报》--2025年7月12日
  • CTFSHOW pwn161 WP
  • 如何成为 PostgreSQL 中级专家
  • 论文学习_SemDiff: Binary Similarity Detection by Diffing Key-Semantics Graphs
  • 4G PPP模式与以太网接口在LwIP中的融合应用
  • JAVA AI智能体——1 入门
  • Redis 基础详细介绍(Redis简单介绍,命令行客户端,Redis 命令,Java客户端)
  • day5--上传视频
  • AI赋能ERP:从自动化到智能化,企业运营的未来已来
  • 【SpringBoot】注册条件+自动配置原理+自定义starter
  • 每天学习一个Python第三方库之jieba库
  • 【DVWA系列】——File Upload——low详细教程(webshell工具冰蝎)
  • on-policy和offpolicy算法
  • 计算机时钟演进:从毫秒到纳秒的精密革命
  • 动态规划题解_零钱兑换【LeetCode】
  • AV1序列头信息
  • Leetcode 3615. Longest Palindromic Path in Graph
  • [Dify]-基础入门5- Dify 中角色设定的正确方式与常见误区
  • SpringBoot3-Flowable7初体验
  • 谷歌在软件工程领域应用AI的进展与未来展望
  • v-for中key值的作用:为什么我总被要求加这个‘没用的‘属性?
  • Linux-网络管理
  • OneCode 3.0 权限引擎实现详解:基于esdright模块的设计与架构
  • 【micro:bit】从入门到放弃(一):在线、离线版本的使用
  • 代码部落 20250713 CSP-J复赛 模拟赛
  • 适配器模式:兼容不兼容接口
  • C++--unordered_set和unordered_map的使用