布隆过滤器为什么能对海量数据去重的学习笔记

本文探讨了C++中set和map的实现,基于红黑树的数据结构,解析了插入操作的源码,并对比了其与unordered_map的优缺点。此外,还介绍了哈希冲突的解决方法,如链表法和开放寻址法。最后,讨论了布隆过滤器在海量数据去重中的应用,包括其工作原理、误差控制以及参数选择的数学公式。
摘要由CSDN通过智能技术生成

缓存场景:请求数据的时候,server 缓存 db
在这里插入图片描述
缓存穿透:redis, mysql 都没有数据存在,黑客可以利用此漏洞不断请求数据导致 mysql 压力过大,如此以来整个系统陷入瘫痪。

场景:用字符串去海量字符串库查询是否存在

set 和 map

  • C++ 标准库(STL) 中的 set 和 map 结构都是采用红黑树实现的,它的增删改查的时间复杂度是 O(logn);

  • k, v ,红黑树是有序的结构, key 是用来比较的字段;红黑树并没有要求 key 字段唯一,在 set 和 map 实现过程限制了 key 字段唯一

    不限制唯一的关键源码

// 这个是截取 nginx 的红⿊树的实现,这段代码是 insert 操作中的⼀部分,执⾏完这个函数还需要检测插⼊节点后是否平衡(主要是看他的⽗节点是否也是红⾊节点)
// 调⽤ ngx_rbtree_insert_value 时,temp传的参数为 红⿊树的根节点,node传的参数为待插⼊的节点
void ngx_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
 	ngx_rbtree_node_t **p;
 	for ( ;; ) {
 		p = (node->key < temp->key) ? &temp->left : &temp->right;// 这⾏很重要
 		if (*p == sentinel) {
 			break;
 		}
 		temp = *p;
 	}
 	*p = node;
     node->parent = temp;
     node->left = sentinel;
     node->right = sentinel;
     ngx_rbt_red(node);
}
// 如果我们插⼊key = 12,如上图红⿊树,12号节点应该在哪个位置? 如果我们要实现插⼊存在的节点变成修改操作,该怎么改上⾯的函数
void ngx_rbtree_insert_value_ex(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
 	ngx_rbtree_node_t **p;
 	for ( ;; ) {
	// {-------------add-------------
 	if (node->key == temp->key) {
 		temp->value = node->value;
 		return;
 	}
	// }-------------add-------------
 	p = (node->key < temp->key) ? &temp->left : &temp->right;// 这⾏很重要
 	if (*p == sentinel) {
 		break;
 	}
 	temp = *p;
 	*p = node;
 	node->parent = temp;
 	node->left = sentinel;
 	node->right = sentinel;
 	ngx_rbt_red(node);
 }

在这里插入图片描述

  • set 和 map 的关键区别是 set 不存储 val 字段

  • 优点:存储效率高,访问速度快

  • 缺点:对于数据量大且查询字符串比较长且查询字符串相似时将会是噩梦

unordered_map

  • C++ 中的 unordered_map 是采用的 hashtable 实现
  • 构成:数组 + hash 函数
  • 他是将字符串通过 hash 函数生成一个整数再映射到数组中;它的增删改查的时间复杂度是 O(1)

在这里插入图片描述

  • hash 函数的作用:避免字符串的比较; hash 函数计算出来的值通过对数组长度的取模 随机分布 在数组当中;
  • hash 函数一般返回的64位整数,将多个大叔映射到一个小数组中,必然回产生冲突

github, murmurhash1, murmurhash2, murmurhash3, siphash ( redis6.0中使用, rust 等大多数语言选用过的 hash 算法来实现 hashmap ),改行为测试地址

  • hash 冲突解决方案:

    • 链表法

      引入链表来处理哈希冲突;在极端情况下,如果冲突元素比较多,该冲突链表过长,这个时候可以将这个链表 (单独这个链表) 转换为 红黑树; 由原来链表时间复杂度 O(n) 转换为红黑树时间 O(logn);

    • 开放寻址法

    这两种都会导致 同类hash聚集; 也就是近似值它的 hash 值也近似,那么它的数组槽位也相近,形成 hash 聚集。

    另外可以用 双重哈希 解决上面出现的 hash 聚集现象

    .net HashTable类的 hash 函数定义如下:

    Hk(key) = [GetHash(key) + k * (1 + (((Gethash*(key) >> 5) + 1) & (hashsize - 1)))] % hashsize;(1 + (((Gethash*(key) >> 5) + 1) & (hashsize - 1))) 与 hashsize 互为素数(两数互为素数表示两者额米有共同的质因子)
        hash + k * hash2
        执行了 hashsize 次探查后,哈希表中的每一个位置都有且只有一次被访问到,也就是说,对于给定的 key, 对哈希表中的同一位置不会同时使用 Hi 和 Hj
    
  • 同样,hashtable 中结点存储了 key 和 value,且没有要求 key 的大小顺序

  • 优点:访问速度更快; 不需要进行字符串比较

  • 缺点:需要引入策略避免冲突,存储效率不高;空间换时间

布隆过滤器

对于海量数据来说,上面两种结构都不适合做海量数据去重,内存不够大。所以引入了布隆过滤器

  • 定义:布隆过滤器是一种概率性数据结构(包括跳表也是),特点是高效的 插入和查询, 能明确告知某个字符串 一定不存在或者可能存在
  • 布隆过滤器相比传统的查询结构(如 hash, set, map ) 等查询结构,更加高效,占用空间更小;但是其缺点是它返回的结果是概率性的,也就是说 结果存在误差 的,虽然这个误差是可控的;同时他也不支持删除操作
  • 组成:位图 (bit) + n 个 hash 函数

在这里插入图片描述

  • 原理:当一个元素加入到位图时,通过k 个 hash 函数将元素映射到位图的 k 个点,并置为1;当检索时,再通过 k 个 hash 函数运算检测位图的 k 个点是否都为 1;如果有不为 1 的店,那么认为不存在;如果全为 1, 则可能存在 (存在误差)
    在这里插入图片描述

  • 在位图中每个槽位只有两种状态 0, 1, 一个槽位被设置为 1 状态,但不明确被设置了多少次,也就是不知道被多少个 str1 哈希映射以及被哪个 hash 函数映射,所以不支持删除操作。

  • 在实际应⽤过程中,布隆过滤器该如何使⽤?要选择多少个hash函数,要分配多少空间的位图,存
    储多少元素?另外如何控制假阳率(布隆过滤器能明确⼀定不存在,不能明确⼀定存在,那么存在
    的判断是有误差的,假阳率就是错误判断存在的概率)?

n -- 布隆过滤器中元素的个数,如上图 只有str1和str2 两个元素 那么 n=2
p -- 假阳率,在0-1之间
m -- 位图所占空间
k -- hash函数的个数

公式如下:
n = c e i l ( m − k l o g ( 1 − exp ⁡ ( l o g ( p ) k ) ) ) p = ( 1 − exp ⁡ ( − k m / n ) ) k m = c e i l ( n ∗ l o g ( p ) l o g ( 1 2 l o g 2 ) ) k = r o u n d ( m n ∗ l o g ( 2 ) ) \begin{aligned} n &= ceil(\frac{m}{\frac{-k}{log(1-\exp(\frac{log(p)}{k}))}}) \\ p &= (1-\exp(\frac{-k}{m / n}))^k \\ m &= ceil(\frac{n * log(p)}{log(\frac{1}{2^{log2}})})\\ k &= round(\frac{m}{n}*log(2)) \end{aligned} npmk=ceil(log(1exp(klog(p)))km)=(1exp(m/nk))k=ceil(log(2log21)nlog(p))=round(nmlog(2))

  • 四个值的关系
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 已知 k ,如何选择 k 个 hash 函数
// 采⽤⼀个hash函数,给hash传不同的种⼦偏移值
// #define MIX_UINT64(v) ((uint32_t)((v>>32)^(v)))
uint64_t hash1 = MurmurHash2_x64(key, len, Seed);
uint64_t hash2 = MurmurHash2_x64(key, len, MIX_UINT64(hash1));
for (i = 0; i < k; i++)
{
 	Pos[i] = (hash1 + i*hash2) % m;
}
// 通过这种⽅式来模拟 k 个hash函数
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Artintel

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

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

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

打赏作者

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

抵扣说明:

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

余额充值