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

C++实现布隆过滤器

1.布隆过滤器概念

位图虽然能高效且节省存储数据,但是类型必须是整型,不能存储其它类型。布隆过滤器是由布隆提出的一中紧凑型,比较巧妙的概率型数据结构,特点是高效的插入和查询,可以得知什么是一定不存在或者可能存在的。它是用多个哈希函数,将一个数据映射到位图结构中。这种方式可以提升查询效率,且节省大量内存空间。

布隆过滤器的思想就是把key先映射成哈希整型值,在映射一个位,但只有一个位,出冲突率就会比较多,可以通过多个哈希函数映射多个位,降低冲突率。

布隆过滤器这里跟哈希表不一样,无法解决哈希冲突,因为布隆过滤器不存储这个值,只标记映射位。

2.布隆过滤器误判率

假设:

m:布隆过滤器的bit长度(数组总长度)

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

k:哈希函数的个数

重点是下图三个等式。

在布隆过滤器中,“哈希函数等条件”通常指的是以下假设和条件:

  1. 哈希函数的独立性:布隆过滤器使用多个哈希函数,这些哈希函数是相互独立的,即一个哈希函数的输出不会影响其他哈希函数的输出。

  2. 哈希函数的均匀性:每个哈希函数将输入元素映射到位数组中的每个位置的概率是相同的,即每个位被设置为1的概率是均匀分布的。

  3. 哈希函数的数量:布隆过滤器中使用了 k 个哈希函数,每个元素都会通过这 k 个哈希函数映射到位数组中。

  4. 位数组的长度:布隆过滤器的位数组长度为 m,即有 m 个位可供哈希函数映射。

  5. 插入的元素数量:布隆过滤器中插入了 n 个元素,每个元素都会通过 k 个哈希函数映射到位数组中。

代码实现

BitSet.h文件

#pragma once
#include<vector>

namespace bit
{
	template<size_t N>
	class bitset
	{
	public:
		bitset()
		{
			_bs.resize(N / 32 + 1);
		}

		// x映射的位标记成1
		void set(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;

			_bs[i] |= (1 << j);
		}

		// x映射的位标记成0
		void reset(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;

			_bs[i] &= (~(1 << j));
		}

		// x映射的位是1返回真
		// x映射的位是0返回假
		bool test(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;

			return _bs[i] & (1 << j);
		}

	private:
		std::vector<int> _bs;
	};

	template<size_t N>
	class twobitset
	{
	public:
		void set(size_t x)
		{
			bool bit1 = _bs1.test(x);
			bool bit2 = _bs2.test(x);

			if (!bit1 && !bit2) // 00->01
			{
				_bs2.set(x);
			}
			else if (!bit1 && bit2) // 01->10
			{
				_bs1.set(x);
				_bs2.reset(x);
			}
			else if (bit1 && !bit2) // 10->11
			{
				_bs1.set(x);
				_bs2.set(x);
			}
		}

		// 返回0 出现0次数
		// 返回1 出现1次数
		// 返回2 出现2次数
		// 返回3 出现2次及以上
		int get_count(size_t x)
		{
			bool bit1 = _bs1.test(x);
			bool bit2 = _bs2.test(x);

			if (!bit1 && !bit2)
			{
				return 0;
			}
			else if (!bit1 && bit2)
			{
				return 1;
			}
			else if (bit1 && !bit2)
			{
				return 2;
			}
			else
			{
				return 3;
			}
		}

	private:
		bitset<N> _bs1;
		bitset<N> _bs2;
	};
};

void test_twobitset()
{
	bit::twobitset<100> tbs;
	int a[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6,6,6,6,7,9 };
	for (auto e : a)
	{
		tbs.set(e);
	}

	for (size_t i = 0; i < 100; ++i)
	{
		//cout << i << "->" << tbs.get_count(i) << endl;
		if (tbs.get_count(i) == 1 || tbs.get_count(i) == 2)
		{
			cout << i << endl;
		}
	}
}

void test_bitset1()
{
	int a1[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6 };
	int a2[] = { 5,3,5,99,6,99,33,66 };

	bitset<100> bs1;
	bitset<100> bs2;

	for (auto e : a1)
	{
		bs1.set(e);
	}

	for (auto e : a2)
	{
		bs2.set(e);
	}

	for (size_t i = 0; i < 100; i++)
	{
		if (bs1.test(i) && bs2.test(i))
		{
			cout << i << endl;
		}
	}
}

 BloomFilter.h文件

示例一是判断字符类型映射是否准确,以及不存在的字符对象是否能判断不存在。示例二是调整参数看误判率的变化。

#pragma once
#include<string>
#include"BitSet.h"

struct HashFuncBKDR
{
	// @detail 本 算法由于在Brian Kernighan与Dennis Ritchie的《The CProgramming Language》
	// 一书被展示而得 名,是一种简单快捷的hash算法,也是Java目前采用的字符串的Hash算法累乘因子为31。
	size_t operator()(const std::string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash *= 31;
			hash += ch;
		}
		return hash;
	}
};


struct HashFuncAP
{
	// 由Arash Partow发明的一种hash算法。  
	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;
	}
};

struct HashFuncDJB
{
	// 由Daniel J. Bernstein教授发明的一种hash算法。 
	size_t operator()(const std::string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash = hash * 33 ^ ch;
		}

		return hash;
	}
};

template<size_t N,
	size_t X = 5,
	class K = std::string,
	class Hash1 = HashFuncBKDR,
	class Hash2 = HashFuncAP,
	class Hash3 = HashFuncDJB>
class BloomFilter
{
public:
	void Set(const K& key)
	{
		size_t hash1 = Hash1()(key) % M;
		size_t hash2 = Hash2()(key) % M;
		size_t hash3 = Hash3()(key) % M;
		//cout << hash1 <<" "<< hash2 <<" "<< hash3 << endl;

		_bs.set(hash1);
		_bs.set(hash2);
		_bs.set(hash3);
	}

	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:
	static const size_t M = N * X;
	bit::bitset<M> _bs;
};

void TestBloomFilter1()
{
	BloomFilter<10> bf;
	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("猪戒八") << endl;
}

void TestBloomFilter2()
{
	srand(time(0));
	const size_t N = 1000000;
	BloomFilter<N> bf;
	//BloomFilter<N, 3> bf;
	//BloomFilter<N, 10> bf;

	std::vector<std::string> v1;
	//std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
	//std::string url = "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=65081411_1_oem_dg&wd=ln2&fenlei=256&rsv_pq=0x8d9962630072789f&rsv_t=ceda1rulSdBxDLjBdX4484KaopD%2BzBFgV1uZn4271RV0PonRFJm0i5xAJ%2FDo&rqlang=en&rsv_enter=1&rsv_dl=ib&rsv_sug3=3&rsv_sug1=2&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&inputT=330&rsv_sug4=2535";
	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;
		}
	}
	cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;

	// 不相似字符串集  前缀后缀都不一样
	v1.clear();
	for (size_t i = 0; i < N; ++i)
	{
		//string url = "zhihu.com";
		string url = "孙悟空";
		url += std::to_string(i + rand());
		v1.push_back(url);
	}

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

	cout << "公式计算出的误判率:" << bf.getFalseProbability() << endl;
}

Test.cpp文件

#include<iostream>
#include<bitset>
using namespace std;

#include"BitSet.h"

//int main()
//{
//	std::bitset<100> bs;
//	cout << sizeof(bs) << endl;
//
//	bs.set(32);
//	bs.set(33);
//
//	bs.reset(33);
//	bs.set(34);
//
//	cout << bs.test(31) << endl;
//	cout << bs.test(32) << endl;
//	cout << bs.test(33) << endl;
//	cout << bs.test(34) << endl;
//	cout << bs.test(35) << endl;
//
//	std::bitset<-1>* ptr = new std::bitset<-1>();
//	//bit::bitset<0xffffffff> bs2;
//	//bit::bitset<UINT_MAX> bs2;
//
//	return 0;
//}

#include"BloomFilter.h"

int main()
{
	//test_bitset1();
	//test_twobitset();

	TestBloomFilter2();
}

3.布隆过滤器删除问题

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

解决方案:用计数标记的方式,一个位置用多个标记位,记录映射这个位的计数值,删除时,仅仅减减计数,那么就可以某种程度支持删除。但是这个方案也有缺陷,如果说一个值不在布隆过滤器中,去删除它,减减映射位的计数,就会影响存在的值,就会导致一个确定存在的值,可能会变成不存在。另一种考虑计数支持删除,但是定期重建一下布隆过滤器。

4.布隆过滤器优缺点

布隆过滤器优点:效率高,节省空间,相比位图,可以适用于各种类型的标记过滤

缺点:存在误判,不好支持删除

5.布隆过滤器在实际应用

爬虫系统中URL去重

在爬虫系统中,为了避免重复爬取相同的URL,可以使用布隆过滤器去重。爬取到的URL可以通过布隆过滤器进行判断,已经存在的URL可以直接忽略,避免重复的网络请求和数据处理。

预防缓存穿透

在分布式缓存系统中,布隆过滤器可以⽤来解决缓存穿透的问题。缓存穿透是指恶意⽤⼾请求⼀个不 存在的数据,导致请求直接访问数据库,造成数据库压⼒过⼤。布隆过滤器可以先判断请求的数据是 否存在于布隆过滤器中,如果不存在,直接返回不存在,避免对数据库的⽆效查询。

对数据库查询提效

在数据库中,布隆过滤器可以⽤来加速查询操作。例如:⼀个app要快速判断⼀个电话号码是否注册过,可以使⽤布隆过滤器来判断⼀个⽤⼾电话号码是否存在于表中,如果不存在,可以直接返回不存在,避免对数据库进⾏⽆⽤的查询操作。如果在,再去数据库查询进⾏⼆次确认。(直接访问数据库会低效率,数据库在磁盘)
http://www.dtcms.com/a/99109.html

相关文章:

  • Linux--进程地址空间
  • Java基础关键_032_反射(二)
  • 六十天前端强化训练之第三十四天之CI/CD 大师级深度解析
  • CNN+Transformer+SE注意力机制多分类模型 + SHAP特征重要性分析,pytorch框架
  • NFS挂载异常排查记录
  • 比kubectl更好用的k8s命令行客户端kube-shell
  • 信号集操作函数
  • Object结构
  • sqli-labs靶场 less 8
  • Display Serializer、Camera Deserializer(Camera Des)和SerDes‌ 加解串应用
  • 【vllm/瞎折腾】在内网wsl上安装vllm并尝试多节点部署(失败了)
  • Java并发编程面试题:线程池Fork/Join(19题)
  • 【深度学习新浪潮】具身智能及其发展前景分析
  • 万物皆可“吉卜力”,使用 AI 创建你的作品
  • 逆向中如何判断JSVMP,及如何解决?(包括实战案例)
  • OpenAI API - Agent
  • protobuf新版下载安装
  • 网络通信微服务
  • 【商城实战(95)】Ansible自动化运维,开启高效部署新篇章
  • 3D Gaussian Splatting部分原理介绍和CUDA代码解读(一)——3D/2D协方差和高斯颜色的计算
  • LeetCode 56. 合并区间 | 排序+贪心解法详解
  • 创意 Python 爱心代码分享
  • el-table 动态给每行增加class属性
  • C++ vector容器总结
  • 诠视科技MR眼镜如何使用VLC 进行RTSP投屏到电脑
  • 【从零实现Json-Rpc框架】- 项目实现 - muduo网络通信类实现篇
  • 黑盒测试的测试用例构成的八点要素
  • 突破数据迁移瓶颈!AWS Snowball如何让PB级数据“瞬间”上云?
  • 12款星光闪光污迹艺术绘画效果Clip Studio Paint笔刷画笔+闪光纹理图片 Clip Studio Glitter Texture Brushes
  • INAV电流计校准