【C++】布隆过滤器

news2024/11/15 16:34:21

文章目录

  • 布隆过滤器提出
  • 布隆过滤器概念
  • 布隆过滤器应用场景
  • 设计思路:
  • 布隆过滤器的插入
  • 布隆过滤器的查找
  • 布隆过滤器删除
  • BloomFilter.h
  • 布隆过滤器优点
  • 布隆过滤器缺陷

布隆过滤器提出

我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容

问题来了,新闻客户端推荐系统如何实现推送去重的?

答:用服务器记录了用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录

那问题又来了,如何快速查找呢?

  1. 用哈希表存储用户记录,缺点:浪费空间
  2. 用位图存储用户记录,缺点:不能处理哈希冲突
  3. 将哈希与位图结合,即布隆过滤器

布隆过滤器概念

位图:节省空间,效率高 ,但是有局限性:只能处理整数 但是布隆过滤器可以处理字符串,自定义类型


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

例子:

image-20220607211952714

布隆过滤器中一个值通过多个哈希函数,在位图中有多个映射位置,即使一个位置发生冲突了,还有另外的映射的值,降低了冲突的概率,由于映射多个位置,因此可能不同的值,处于同一个位置,虽然不能保证这个值一定存在,但是可以保证一个值一定不存在,因为只要有一个映射的位置为0,就说明该值不存在


问题:布隆过滤器会误判存在还是会误判不存在?

  • 存在可能会误判! 因为会产生哈希冲突,不同的字符串可能会映射在同一个位置, 一个字符串不存在但是可能会被误判成存在
  • 为了减少减少误判,通常采用多个哈希函数来映射多个位置 即一个值映射多个位置

是一定会有哈希冲突的!!!因为整数是有限的,字符串是无限的


布隆过滤器应用场景

布隆过滤器会存在误判,所以通常应用在允许误判的场景之中

一般应用场景:数据量大,节省空间,允许误判

  • 黑名单校验
    发现存在黑名单中的,就执行特定操作,比如:识别垃圾邮件,只要是邮箱在黑名单中的邮件,就识别为垃圾邮件,假设黑名单的数量是数以亿计的,存放起来就是非常耗费存储空间的,布隆过滤器则是一个较好的解决方案,把所有黑名单都放在布隆过滤器中,再收到邮件时,判断邮件地址是否在布隆过滤器中即可,

  • 身份验证:
    大门口的身份验证,如果不是小区里面的人,直接就拒绝进入(不在是确定的),如果通过了布隆过滤器的判断,再去数据库中对比一次,这样通过一层布隆过滤器可以提高这个查找系统的效率

  • 检测手机号是否注册过

    系统所有用户的电话号码都存储再数据库的用户表 . 如果这个手机号不在布隆过滤器就肯定没有注册过, 如果在布隆过滤器,那么这里可能存在误判,再查一次数据库复核一下


设计思路:

首先我们需要确定位图开辟多大的空间

image-20220607212810282

假设k为3, 则->位图的大小大致应该为: m = 4.2*n


先准备几个哈希函数用于将字符串转为整形

//BKDR算法
struct BKDRHash
{
	size_t operator()(const string& s)
	{
		// BKDR
		size_t value = 0;
		for (auto ch : s)
		{
			value *= 31;
			value += ch;
		}
		return value;
	}
};

struct APHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (long 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 DJBHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};

template<size_t N>	非类型模板参数,共插入多少个值
size_t X = 4,	//X值越大,产生冲突的概率越低, N*X表示位图开辟多少个比特位空间,利用公式计算X
class K = string,
class HashFunc1 = BKDRHash,//一个字符串想映射几个比特位就给几个HashFunc仿函数计算哈希地址
class HashFunc2 = APHash,
class HashFunc3 = DJBHash>
    
class BloomFilter
{
public:
private:
	bitset<X*N> _bs;
};

布隆过滤器的插入

将值对应的每个哈希函数计算出的位置都置为1

//将key所映射的位置设为1
void Set(const K& key)
{
    size_t len = N * X;//总长度 
    //计算映射的哈希地址 HashFunc()是匿名对象
    size_t index1 = HashFunc1()(key) % len;
    size_t index2 = HashFunc2()(key) % len;
    size_t index3 = HashFunc3()(key) % len;

    //将对应的映射位置标志为1
    _bs.set(index1);
    _bs.set(index2);
    _bs.set(index3);
}

布隆过滤器的查找

布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中 因此被映射到的位置的比特位一定为1,所以可以按照以下方式进行查找:

  • 分别计算每个哈希值对应的比特位置存储的是否为0, 只要有一个映射位置为0代表该元素一定不在哈希表中
  • 否则可能在哈希表中(因为存在误判)
//判断key是否在布隆过滤器中
bool Test(const K& key)
{
    //三个映射为都是1才在(可能存在误判),其中一个不是1就不在
    size_t len = N * X;
    size_t index1 = HashFunc1()(key) % len;
    size_t index2 = HashFunc2()(key) % len;
    size_t index3 = HashFunc3()(key) % len;
    if ( (!_bs.test(index1)) || (!_bs.test(index2)) || (!_bs.test(index3)))
    {
        return false;
    }
    return true;//可能存在误判	
}

注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在, 如果**该元素存在时,该元素可能存在也可能不存在.**因为有些哈希函数存在一定的误判


布隆过滤器删除

布隆过滤器不能直接支持删除工作, 因为在删除一个元素时可能会影响其他元素,因为不确定当前位置,是自己的,还是发生了哈希冲突其它的值映射过来的

如何支持修改呢? ->存储引用计数(有几个值映射在当前位置)

将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一, 删除元素时给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作

但是这种方法不太好, 因为计数器的大小不易确定,如果给小了,发生冲突会导致溢出(计数回绕:即最大值溢出变为 最小值) 如果给大了浪费空间,脱离了布隆过滤器的本质思想,

缺陷:

  1. 无法确认元素是否真正在布隆过滤器中
  2. 存在计数回绕

BloomFilter.h

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once


//BKDR算法
struct BKDRHash
{
	size_t operator()(const string& s)
	{
		// BKDR
		size_t value = 0;
		for (auto ch : s)
		{
			value *= 31;
			value += ch;
		}
		return value;
	}
};

struct APHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (long 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 DJBHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};

template<size_t N,	非类型模板参数,共插入多少个值
size_t X = 4,	//X值越大,产生冲突的概率越低, N*X表示位图开辟多少个比特位空间
class K = string,// 假设布隆过滤器中元素类型为K,默认为string类型
//每个元素对应3个哈希函数
class HashFunc1 = BKDRHash,//一个字符串想映射几个比特位就给几个HashFunc仿函数计算哈希地址
class HashFunc2 = APHash,
class HashFunc3 = DJBHash>
class BloomFilter
{
public:
	//将key所在的三个映射位设为1
	void Set(const K& key)
	{
		size_t len = N * X;//总长度 
		//计算映射的哈希地址 HashFunc()是匿名对象
		size_t index1 = HashFunc1()(key) % len;
		size_t index2 = HashFunc2()(key) % len;
		size_t index3 = HashFunc3()(key) % len;

		//将对应的映射位置标志为1
		_bs.set(index1);
		_bs.set(index2);
		_bs.set(index3);
	}
	//判断key是否在布隆过滤器中
	bool Test(const K& key)
	{
		//三个映射为都是1才在(可能存在误判),其中一个不是1就不在
		size_t len = N * X;
		size_t index1 = HashFunc1()(key) % len;
		size_t index2 = HashFunc2()(key) % len;
		size_t index3 = HashFunc3()(key) % len;
 		if ( (!_bs.test(index1)) || (!_bs.test(index2)) || (!_bs.test(index3)))
		{
			return false;
		}
		return true;//可能存在误判	
	}
// 不支持删除,删除可能会影响其他值,
// 一般情况不支持删除,why?->多个值可能会标记一个位,删除可能会影响其他key
// 如果非要支持删除的话,标记不再使用一个比特位,可以使用多个比特位,进行计数多少个值映射的这个比特位
// 但是这种方法是杀敌一千,自损八百的做法,因为消耗的更多的空间
	void Reset(const K& key);
private:
    //此时k为3 则此时位图的大小大致应该为: m = 4.2*n 即X = 4
	bitset<N*X> _bs;
};
void TestBloomFilter()
{
	BloomFilter<100> bf;//最多存100个值 -》开辟100*4个比特位
	srand(time(0));
	size_t N = 100;
	std::vector<std::string> v2;
	for (size_t i = 0; i < N; ++i)
	{
		std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
		url += std::to_string(6789 + i);
		v2.push_back(url);
	}

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

布隆过滤器优点

  1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关

  2. 哈希函数相互之间没有关系,方便硬件并行运算

  3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势

  4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势

  5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能

  6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算


布隆过滤器缺陷

  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
  2. 不能获取元素本身
  3. 一般情况下不能从布隆过滤器中删除元素
  4. 如果采用计数方式删除,可能会存在计数回绕问题

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

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

相关文章

Leetcode力扣秋招刷题路-0902

从0开始的秋招刷题路&#xff0c;记录下所刷每道题的题解&#xff0c;帮助自己回顾总结 902. 最大为 N 的数字组合 给定一个按 非递减顺序 排列的数字数组 digits 。你可以用任意次数 digits[i] 来写的数字。例如&#xff0c;如果 digits [‘1’,‘3’,‘5’]&#xff0c;我…

一图看懂 requests 模块:用Python编写、供人类使用的HTTP库, 资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 requests 模块&#xff1a;用Python编写、供人类使用的HTTP库, 资料整理笔记&#xff08;大全&#xff09; 摘要模块图类关系图模块全展开【requests】统计常量str 模块3 w…

小红书违禁词有哪些,小红书违禁词汇总分享

大家都知道小红书平台对于违禁词的管控一向非常严格&#xff0c;笔记中一旦出现就可能被限流&#xff0c;今天为大家整理了一份小红书违禁词汇总&#xff0c;希望能够帮助大家避免被限流。 小红书违禁词汇总大致有以下几个分类&#xff0c;大家平时写笔记的时候最好避开这些词或…

HashMap底层实现原理

HashMap HashMap 最早出现在 JDK 1.2中&#xff0c;底层基于散列算法实现&#xff0c;它是一个key-value结构的容器。 是一个key-value的映射容器&#xff0c;key不重复jdk8中的HashMap基于数组链表红黑树实现不保证键值的顺序可以存入null值非线程安全&#xff0c;多线程环境…

log4j2.xml配置解析

log4j2.xml文件的配置大致如下&#xff1a; Configuration&#xff1a;为根节点&#xff0c;有status和monitorInterval等多个属性 status的值有 “trace”, “debug”, “info”, “warn”, “error” and “fatal”&#xff0c;用于控制log4j2日志框架本身的日志级别&#x…

python+vue+nodejs旅游资源信息网站

1&#xff0e;系统登录&#xff1a;系统登录是用户访问系统的路口&#xff0c;设计了系统登录界面&#xff0c;包括用户名、密码和验证码&#xff0c;然后对登录进来的用户判断身份信息&#xff0c;判断是管理员用户还是普通用户。 2&#xff0e;系统用户管理&#xff1a;不管是…

如何选择正确的数据可视化图表

数据可视化是数据分析的重要组成部分&#xff0c;因为它们能够以图形格式有效地汇总大量数据。有许多可用的图表类型&#xff0c;每种类型都有自己的优势和用例。分析过程中最棘手的部分之一是选择使用这些可视化效果之一的正确方法来表示数据。 在本文中&#xff0c;我们根据需…

基于redis和threadlocal实现登录状态校验和拦截

1.流程图 单机节点下的登录状态校验 分布式节点下的登录状态校验 2.代码实现 实现步骤分为如下几步 实现WebMvcConfigurer接口&#xff0c;添加拦截器定义拦截器&#xff0c;需要配置两个interceptor&#xff0c;第一个用于刷新token&#xff0c;写threadlocal&#xff…

AI绘图实战(八):制作游戏人物原稿三视图 | Stable Diffusion成为设计师生产力工具

S&#xff1a;AI能取代设计师么&#xff1f; I &#xff1a;至少在设计行业&#xff0c;目前AI扮演的主要角色还是超级工具&#xff0c;要顶替&#xff1f;除非甲方对设计效果无所畏惧~~ 预先学习&#xff1a; 安装及其问题解决参考&#xff1a;《Windows安装Stable Diffusion …

【机器学习 - 10】:PCA和梯度上升法

文章目录 了解PCA使用梯度上升法求解第一主成分使用梯度上升法求解第二主成分求数据前n个主成分使用sklearn中封装的PCA使用真实数据集 了解PCA PCA的概念&#xff1a;主成分分析(Principal Component Analysis&#xff0c;PCA)&#xff0c;是一种统计方法。通过正交变换将一组…

10分钟如何轻松掌握JMeter使用方法?

目录 引言 安装jmeter HTTP信息头管理器 JMeter断言 HTTP请求默认值来代替所有的域名与端口 JSON提取器来替换变量 结语 引言 想要了解网站或应用程序的性能极限&#xff0c;JMeter是一个不可或缺的工具。但是&#xff0c;对于初学者来说&#xff0c;该如何上手使用JMe…

【LLM】低成本部署大语言模型, 并且还能达到部署在GPU上差不多的效果

目录 前言 部署 效果 问题1&#xff1a;人类为什么需要睡觉&#xff1f; 问题2&#xff1a;世界上最高的山峰是什么&#xff1f; 前言 点进来看本文的应该都知道模型对硬件的要求很高, 那我也不废话了, 直接安排最近发现的一个开源项目, 它可以帮助我们降低部署模型的成…

按摩仪市场的AB面:暗战不止,迷雾未散

配图来自Canva可画 由于生活节奏的加快以及来自各方的压力&#xff0c;再加上熬夜、长时间低头玩手机等不良生活习惯&#xff0c;导致不少人的身体都出现了亚健康状态。不过&#xff0c;随着当下健康理念逐渐深入人心&#xff0c;人们对于健康的重视程度也持续提升。无论是刘畊…

如何挖到人生中第一个漏洞?保姆级漏洞挖掘教学

前言 有不少阅读过我文章的伙伴都知道&#xff0c;我从事网络安全行业已经好几年&#xff0c;积累了丰富的经验和技能。在这段时间里&#xff0c;我参与了多个实际项目的规划和实施&#xff0c;成功防范了各种网络攻击和漏洞利用&#xff0c;提高了安全防护水平。 也有很多小…

Java微服务商城高并发秒杀项目--013.SentinelResource的使用

在shop-order-server模块中新建AnnoController&#xff1a; RestController Slf4j public class AnnoController {RequestMapping("/anno1")SentinelResource(value "anno1",blockHandler"anno1BlockHandler",fallback "anno1Fallback&qu…

使用Stream流写出优雅的高质量代码

前言 我们在开发中会大量的用到集合&#xff0c;少不了对集合进行一些操作&#xff0c;如何优雅的遍历集合&#xff0c;操作集合&#xff0c;不仅能体现出代码的质量&#xff0c;更能体现出程序员本身对自我的要求。 文章目录 前言一、Stream初体验二、Stream流的使用2.1 获取…

python实现人脸识别(face_recognition)

一、定义 1、介绍 本项目是世界上最强大、简洁的人脸识别库&#xff0c;你可以使用Python和命令行工具提取、识别、操作人脸。 本项目的人脸识别是基于业内领先的C开源库dlib中的深度学习模型&#xff0c;用Labeled Faces in the Wild人脸数据集进行测试&#xff0c;有高达99…

ESP32(一):Win10配置 IDF+VSCode

一、安装包下载&#xff1a; Git&#xff1a;Git for WindowsPython&#xff1a;Download Python | Python.org以Windows x86-64开头的是 64 位的 Python 安装程序&#xff1b;以Windows x86开头的是 32 位的 Python 安装程序。ESP-IDF&#xff08;选择Offline版本&#xff09…

Kettle安装与使用

一、Kettle简介 Kettle最早是一个开源的ETL&#xff08;Extract-Transform-Load的缩写&#xff09;工具&#xff0c;全称为KDE Extraction, Transportation, Transformation and Loading Environment。后来Kettle重命名为Pentaho Data Integration 。它由Java开发&#xff0c;…

数据结构学习记录——堆的删除(思路图解、代码实现、逐段解析)

目录 堆的删除&#xff08;最大堆&#xff09; 思路 代码 解析 堆的删除&#xff08;最大堆&#xff09; 思路 代码 ElementType DeleteMax( MaxHeap H ) { /* 从最大堆H中取出键值为最大的元素&#xff0c;并删除一个结点 */int Parent, Child;ElementType MaxItem, X…