C++实现布隆过滤器

news2025/2/1 17:04:06

目录

一、什么是布隆过滤器

二、布隆过滤器的映射

三、布隆过滤器的作用

四、布隆过滤器的实现

五、总结+测试


一、什么是布隆过滤器

之前我们学习了位图,我们知道位图主要是实现了整形的映射bit位,这样可以大幅度的节省空间,那么针对于我们也经常用的string类型,或者其他类型,该如何去映射呢?

可以说string类的个数是无限的,无线个数去映射到有限的位置,那么必定会发生哈希冲突,这是无法避免的,我们有没有办法去减少这种哈希冲突呢?

大佬布隆就提出了布隆过滤器。(不是弗雷尔卓德之心)

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间

二、布隆过滤器的映射

比如说,如下有三个string字符串,分别为sort,left,right,利用哈希的思想,我们可以将他们映射到相应的位置,这里跟位图一样,映射到了bit位,就算我们哈希函数设置得再好,也无法避免哈希冲突。

比如下面sort和left发生了哈希冲突,如果我现在先插入了sort,将该bit位置为了1,当我去查询left的时候,会发现left映射的bit位为1,我们就会以为left存在,实际上left并没有存在,这样就会导致误判的发生。

大佬布隆提出了一个思想,我可以给字符串使用更多的哈希函数,同时映射更多的位置, 这些位置相互验证,几个映射位置一同存在,这样会更具有可靠性(虽然他也还没完全解决可靠问题,也无法解决)

具体情况如下,sort映射到了5,16,18 ,left映射到5,11,13, right映射到16,20,26。

这样映射后,虽然浪费了一些空间,但就算某个映射发生重复了,也没关系,只要不是三个位置都冲突就没事,大大的降低了冲突概率。(也可以选择更多的映射,但同时也会浪费更多的空间)

三、布隆过滤器的作用

前面我们分析了那么多,感觉这个布隆过滤器没啥用啊,始终有概率发生冲突,是不是在实际中用不上啊,大家别急,我们先来分析一下布隆过滤器的可靠性问题。

  • 如果该string不存在,那么该bit位还没被映射,证明确实不存在,这肯定是可靠的。
  • 如果该string存在,他映射的位置都存在,这有可能是其他string映射到这里的,这会导致告诉我们的是虚假消息(本来不存在,你说存在),这是不可靠的。

不存在一定可靠,存在可能是虚假消息。根据这点,我们来看下面的用例 

        我爱玩英雄联盟,在我们玩LOL之前,都需要取名,并且每一个区名字不可以重复,用户名这个数据一般都存放在服务器,如果每一个输入的名字,我们都要去服务器里面判断是否存在,再返回给用户,这样就会牵扯到网络相关知识,同时也会让服务器访问变多。

        如果在客户端里面塞一个布隆过滤器,在服务器启动时,往布隆过滤器里添加用户名的映射关系,如果用户名不存在,是可靠的,那么我就告诉你,这个名字可以取。如果用户名映射关系告诉我们存在,这是不可靠的,他可能不存在,因此只要映射到存在,我们就去服务器再寻找一下,看能不看找到,结果再返回给用户。

        这样既不会让客户端大很多,也不会让服务器承担很多,是不是一举两得。这也是为什么我们叫他过滤器的原因,他的作用是过滤。

四、布隆过滤器的实现

 这里我们BloomFilter类模板参数N,和类型K,还有三个哈希函数类,变量_bs使用了库里面的bitset来构建。

set函数就是分别计算三个哈希函数的映射值,再用_bs.set()置为1,Test函数就是分别判断_bs.test()是否为true,有一个是false就会返回false(这是准确的),三个都为true才会返回true(这是不准的)。

#pragma once
#include<bitset>
#include<string>
template<size_t N,
	class K,
	class HashFunc1
	class HashFunc2
	class HashFunc3>
class BloomFilter
{
public:
	void Set(const K& key)
	{
		HashFunc1 kf1;
		size_t hash1 = kf1(key) % N;
		size_t hash2 = HashFunc2()(key) % N;
		size_t hash3 = HashFunc3()(key) % N;
		
		_bs.set(hash1);
		_bs.set(hash2);
		_bs.set(hash3);
	}

	bool Test(const K& key)
	{
		//false是准确的
		size_t hash1 = HashFunc1()(key) % N;
		if (_bs.test(hash1) == false)
			return false;
		size_t hash2 = HashFunc2()(key) % N;
		if (_bs.test(hash2) == false)
			return false;
		size_t hash3 = HashFunc3()(key) % N;
		if (_bs.test(hash3) == false)
			return false;

		//可能误判
		return true;
	}
private:
	bitset<N> _bs;
};

一般K为string,我们就可以给他一个string的缺省值,至于三个哈希函数我们去大佬博客字符串哈希函数拷贝三个过来使用即可。

哈希函数如下

struct BKDRHash
{
	size_t operator()(const string& key)
	{
		// BKDR
		size_t hash = 0;
		for (auto e : key)
		{
			hash *= 31;
			hash += e;
		}

		return hash;
	}
};

struct APHash
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (size_t i = 0; i < key.size(); i++)
		{
			char ch = key[i];
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

struct DJBHash
{
	size_t operator()(const string& key)
	{
		size_t hash = 5381;
		for (auto ch : key)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};

修改一下缺省值 

测试一下啊,运气还算不错,没有冲突 

五、总结+测试

我们将将数据放大一下,看看冲突情况,这里我们开辟了十倍的空间,发现冲突还算比较小。

现在开辟了5倍的空间,发现冲突就变大了一些。

总结:适当的多开辟一些空间,会让误判率变得较小,足够多甚至可能会没有冲突,但同样也会浪费很多空间。但是这毕竟不算可靠,始终需要去数据库里再判断,因此适当开辟就好。

 最后附上总代码BloomFilter.h

#pragma once
#include<bitset>
#include<string>

struct BKDRHash
{
	size_t operator()(const string& key)
	{
		// BKDR
		size_t hash = 0;
		for (auto e : key)
		{
			hash *= 31;
			hash += e;
		}

		return hash;
	}
};

struct APHash
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (size_t i = 0; i < key.size(); i++)
		{
			char ch = key[i];
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

struct DJBHash
{
	size_t operator()(const string& key)
	{
		size_t hash = 5381;
		for (auto ch : key)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};
template<size_t N,
	class K = string,
	class HashFunc1 = BKDRHash,
	class HashFunc2 = APHash,
	class HashFunc3 = DJBHash>
class BloomFilter
{
public:
	void Set(const K& key)
	{
		HashFunc1 kf1;
		size_t hash1 = kf1(key) % N;
		size_t hash2 = HashFunc2()(key) % N;
		size_t hash3 = HashFunc3()(key) % N;
		
		_bs.set(hash1);
		_bs.set(hash2);
		_bs.set(hash3);
	}

	bool Test(const K& key)
	{
		//false是准确的
		size_t hash1 = HashFunc1()(key) % N;
		if (_bs.test(hash1) == false)
			return false;
		size_t hash2 = HashFunc2()(key) % N;
		if (_bs.test(hash2) == false)
			return false;
		size_t hash3 = HashFunc3()(key) % N;
		if (_bs.test(hash3) == false)
			return false;

		//可能误判
		return true;
	}
private:
	bitset<N> _bs;
};

test.cpp 

#include<iostream>
using namespace std;
#include"BloomFilter.h"

void test01()
{
	BloomFilter<100> bf;
	bf.Set("猪八戒");
	bf.Set("沙悟净");
	bf.Set("孙悟空");
	bf.Set("二郎神");

	cout << bf.Test("猪八戒") << endl;
	cout << bf.Test("沙悟净") << endl;
	cout << bf.Test("孙悟空") << endl;
	cout << bf.Test("二郎神") << endl;
	cout << bf.Test("二郎神1") << endl;
	cout << bf.Test("二郎神2") << endl;
	cout << bf.Test("二郎神 ") << endl;
	cout << bf.Test("太白晶星") << endl;
}

void test02()
{
	srand(time(0));
	const size_t N = 100000;
	BloomFilter<N * 5> bf;

	std::vector<std::string> v1;
	//std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
	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是相似字符串集(前缀一样),但是不一样
	std::vector<std::string> v2;
	for (size_t i = 0; i < N; ++i)
	{
		std::string urlstr = url;
		urlstr += std::to_string(9999999 + i);
		v2.push_back(urlstr);
	}

	size_t n2 = 0;
	for (auto& str : v2)
	{
		if (bf.Test(str)) // 误判
		{
			++n2;
		}
	}
	cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;

	// 不相似字符串集
	std::vector<std::string> v3;
	for (size_t i = 0; i < N; ++i)
	{
		//string url = "zhihu.com";
		string url = "孙悟空";
		url += std::to_string(i + rand());
		v3.push_back(url);
	}

	size_t n3 = 0;
	for (auto& str : v3)
	{
		if (bf.Test(str))
		{
			++n3;
		}
	}
	cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;

}

int main()
{
	//test01();
	test02();
}

 谢谢大家观看!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1329300.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【笔记】Spring是什么

什么是spring&#xff1f; Spring的基础知识铺垫 IOC AOP<-Spring->容器->生态 先说你的认知&#xff0c;总-分结构 spring是一个基础的框架&#xff0c;同时提供了Bean的容器&#xff0c;用来方便装载具体的Bean对象&#xff0c;之前在使用对象的时候必须自己new&…

代码随想录第三十八天(一刷C语言)|零钱兑换II组合总数和 IV

创作目的&#xff1a;为了方便自己后续复习重点&#xff0c;以及养成写博客的习惯。 一、零钱兑换II 思路&#xff1a;参考carl文档 1、确定dp数组以及下标的含义&#xff1a;凑成总金额j的货币组合数为dp[j]。 2、确定递推公式&#xff1a;dp[j] 就是所有的dp[j - coins[i…

中国ESG的新故事:主动、常态与变革

ESG的终局不仅仅是与业务的结合&#xff0c;而是需要将ESG 融入企业价值内核&#xff0c;实现社会价值与商业价值的深度融合&#xff0c;即有意义地盈利。 作者|斗斗 编辑|皮爷 出品|产业家 “到这里来吧&#xff0c;我将帮你们获得这个世界。我的文明已无力解决自己的…

微服务之配置中心与服务跟踪

zookeeper 配置中心 实现的架构图如下所示&#xff0c;采取数据加载到内存方式解决高效获取的问题&#xff0c;借助 zookeeper 的节点监听机制来实现实时感知。 配置中心数据分类 事件调度&#xff08;kafka&#xff09; 消息服务和事件的统一调度&#xff0c;常用用 kafka …

pytorch张量的创建

张量的创建 张量&#xff08;Tensors&#xff09;类似于NumPy的ndarrays &#xff0c;但张量可以在GPU上进行计算。从本质上来说&#xff0c;PyTorch是一个处理张量的库。一个张量是一个数字、向量、矩阵或任何n维数组。 import torch import numpy torch.manual_seed(7) # 固…

linux系统和网络(二):进程和系统时间

本文主要探讨linux系统进程和系统相关知识&#xff0c;本博客其他博文对该文章的部分内容有详细介绍 main函数 int main(int argc,char *argv[],char *envp[]); 操作系统下main执行前先执行引导代码,编译连接引导代码和程序连接在一起构成可执行程序,加载器将程序加载到内存中…

react 2

1.快速搭建开发环境 2.react渲染流程 3.1 jsx基础 概念 3.2 jsx基础 本质 3.3 jsx基础 jsx表达式 3.4 jsx基础 实现列表渲染 3.5 jsx基础 实现条件渲染 3.5 jsx基础 实现复杂的条件渲染 4. react中事件绑定 5.react组建基础使用 6.1 useState 6.2 useState修改状态的规则 7.基础…

渗透测试和漏洞扫描有什么区别

渗透测试和漏洞扫描是网络安全领域中非常重要的两种技术手段&#xff0c;它们都可以帮助组织或企业发现和修复系统中的漏洞和弱点。然而&#xff0c;这两种技术手段在目的、深度、方法和时间和成本等方面存在显著的区别。 首先我们来了解下渗透测试和漏洞扫描分别是什么&#x…

测试开发体系介绍——测试体系介绍-L1

目录&#xff1a; 软件测试基础概念 软件测试:软件测试作用:软件缺陷:软件测试原则:软件测试对象:测试用例软件开发流程 软件:软件生命周期:软件开发流程:瀑布模型:瀑布模型优缺点敏捷开发模型: XP - 极限编程:SCRUM:DevOps&#xff1a;DevOps 生命周期&#xff1a;DevOps 对发…

C语言中关于操作符的理解

本篇文章只会列出大家在生活中经常使用的操作符 算术操作符 在算数操作符中常用的有&#xff0c;&#xff0c;-&#xff0c;*&#xff0c;/&#xff0c;% &#xff0c;我们重点讲一讲 / (除) 和 % (模) " / "运算 #include <stdio.h>int main() {int a5/2;fl…

【Amazon 实验③】使用Amazon WAF做基础 Web Service 防护之速率策略

文章目录 1. 速率策略1.1 介绍 2. 实验步骤2.1 添加规则2.2 测试2.3 结果 通过上一篇文章大家了解到如何使用Amazon WAF做关于自定义规则设置的 Web Service 防护【Amazon 实验②】使用Amazon WAF做基础 Web Service 防护之自定义规则&#xff0c;本篇文章将继续讲解一下关于速…

WebGL开发三维解剖学应用

开发基于 WebGL 的三维解剖学应用通常涉及以下步骤。这些步骤包括创建三维模型、整合交互性、优化性能等&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.三维模型创建&#xff1a; 首先&#xff0…

SpringBoot 使用Quartz执行定时任务对象时无法注入Bean问题

文章目录 问题描述解决方案结束语 大家好&#xff01;今天是2023年12月21日 | 农历十一月初九(距离2024年还有一周左右的时间)&#xff0c;最近还是比较忙的&#xff0c;忙着搞钱&#xff0c;毕竟马上过年啦&#xff01; 问题描述 感谢大家对我一直以来的支持与帮助&#xff0c…

7.串口通信uart编写思路及自定义协议

前言&#xff1a; 串口是很重要的&#xff0c;有许多模块通信接口就是串口&#xff0c;例如gps模块&#xff0c;蓝牙模块&#xff0c;wifi模块还有一些精度比较高的陀螺仪模块等等&#xff0c;所以学会了串口之后&#xff0c;这些听起来很牛批的模块都能够用起来了。此外&#…

Qt/QML编程学习之心得:在QML工程中添加库(十四)

实现库并且使用库&#xff0c;类似于vc中的静态库library、动态库dll、COM组件等方法一样&#xff0c;在Qt中也经常会使用库&#xff0c;或者将部分功能打包成库。 右击Qt项目&#xff0c;点击add library... 在linux中将.a文件导入&#xff0c;工程会自动在.pro温江中增加相应…

centos安装Jenkins并拉取git远程仓库的代码进行自动化构建部署

安装Jenkins并拉取git远程仓库的代码进行自动化构建部署 1 前置条件2 先安装jdk113 安装git4 安装maven5 安装jenkins5.1下载jenkins5.2启动jenkins 6 使用jenkins拉取git仓库代码并部署6.1 安装插件6.2 在jenkins中配置maven6.3在jenkins上构建maven项目6.4 配置拉取的git仓库…

成功案例分享:物业管理小程序如何助力打造智慧社区

随着科技的进步和互联网的普及&#xff0c;数字化转型已经渗透到各个行业&#xff0c;包括物业管理。借助小程序这一轻量级应用&#xff0c;物业管理可以实现线上线下服务的无缝对接&#xff0c;提升服务质量&#xff0c;优化用户体验。本文将详细介绍如何通过乔拓云网设计小程…

【vtkWidgetRepresentation】第十六期 vtkContourRepresentation(三)

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享vtkContourLineInterpolator接口的源码剖析和实例应用,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 目录 前言 …

csrf自动化检测调研

https://github.com/pillarjs/understanding-csrf/blob/master/README_zh.md CSRF 攻击者在钓鱼站点&#xff0c;可以通过创建一个AJAX按钮或者表单来针对你的网站创建一个请求&#xff1a; <form action"https://my.site.com/me/something-destructive" metho…

【Java基础】 一个空的Object对象到底占多少内存

对象头包括&#xff08;Markword、类元指针、数组长度&#xff09; 压缩指针ON&#xff1a;占用12字节&#xff0c;Markword占8字节、类元指针占4字节.但是为了避免伪共享问题&#xff0c;JVM会按照8字节的倍数填充&#xff0c;所以会在对其区填充4字节&#xff0c;变成16字节。…