海量数据去重-由BitMap引出的布隆过滤器

布隆过滤器(图文无关)

本文参考资源:

那些惊艳的算法们(一)——布隆过滤器_C/C++_xinzhongtianxia的博客-CSDN博客

详解布隆过滤器的原理、使用场景和注意事项 - 简书

概述

昨天学习了BitMap这种数据结构(算法),链接:海量数据算法-BitMap介绍和实现

然而BitMap也存在着一些问题:

  1. 当样本分布极度不均匀的时候,BitMap会造成很大空间上的浪费。

举个例子,比如你有10个数,分别是1、2、3、4、5、6、7、8、99999999999;那么你不得不用99999999999个bit位去实现你的BitMap,而这个BitMap的中间绝大多数位置都是0,并且永远不会用到,这显然是极度不划算的。

  1. 当元素不是整型的时候,BitMap就不适用了。
    想想看,你拿到的是一堆url,然后如果你想用BitMap做去重的话,先得把url转换成int型,在转换的过程中难免某些url会计算出相同的int值,于是BitMap的准确性就会降低。

那针对这两种情况有没有解决办法呢?

第一种分布不均匀的情况可以通过hash函数,将元素都映射到一个区间范围内,减少大段区间闲置造成的浪费,这很简单,取模就好了,难的是取模之后的值保证不相同,即不发生hash冲突。

第二种情况,把字符串映射成整数是必要的,那么唯一要做的就是保证我们的hash函数尽可能的减少hash冲突,一次不行我就多hash几次,hash还是容易碰撞,那我就扩大数组的范围,使hash值尽可能的均匀分布,减少hash冲突的概率。

基于这种思想,BloomFilter(布隆过滤器)诞生了。

原理

比如现在有10000个字符串,要进行去重操作,如果用bitmap的方法将字符串的哈希值对应到一个bit,由于字符串的哈希值不是唯一的,可能出现哈希冲突,而若两个字符串哈希冲突了,就会对应到同一个bit上,bitmap就会误判这两个字符串相等。

解决的方法就是,使用多个哈希函数,例如使用三个哈希函数,每个字符串可以计算出三个哈希映射,将所有映射的位标为1,而判断有没有重复的方法是,只要得到的三个映射位不是都已为1了,就认为该字符串没有重复。

海量数据去重-由BitMap引出的布隆过滤器

海量数据去重-由BitMap引出的布隆过滤器

可见布隆过滤器相较于BitMap功能较单一,无法根据已有的位倒推回原始数据,只能用作查询去重功能。

但这也不意味着布隆过滤器就不会误判,假如一个新的无重复的字符串,映射的位在之前就被其他字符串得到的哈希映射都标为了1,那么布隆过滤器也认为它是重复的。不过误判的概率较小(比BitMap小多了),在大部分生产环境下是可以接受的(因为有的时候不重复的字符串没多少,反而重复的字符串占了大多数,主要目的只是不用反复处理重复的字符串)。

参数调优

由上面的原理可知,对于布隆过滤器性能最重要的参数有两个:

  • bit数组的大小,bit越多且哈希映射分布均匀的条件下,哈希冲突的概率是越低的。
  • 哈希函数的个数,哈希函数的个数如果太少,更容易冲突,而如果哈希函数的个数太多,则将bit数组内的元素标为1的进度也会加快,从而造成哈希冲突。所以哈希函数的个数需要均衡。

误判率和数组bit个数的关系

关于具体的误判率计算维基百科给出了计算过程:Bloom filter - Wikipedia

对于一个期望的误判率p,期望的插入元素个数n,最佳数组bit个数m和哈希函数数量k的计算公式如下:

最佳数组bit个数

最佳哈希函数数量

所以当构造一个布隆过滤器的时候并且希望误判率可控时通常要传入期望的误判率、期望的插入元素个数等参数。

谷歌有java中BloomFilter的实现,有兴趣的可以自己去研究一下:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>25.1-jre</version>
</dependency>

应用

  • 重复URL的过滤
  • 邮箱黑名单的去重
  • 推荐去重

Redis4.0后开始以插件的形式支持布隆过滤器(安装后使用指令bf.addbf.exist

Redis防止缓存穿透就可以使用布隆过滤器,日后再谈。

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/%e6%b5%b7%e9%87%8f%e6%95%b0%e6%8d%ae%e5%8e%bb%e9%87%8d-%e7%94%b1bitmap%e5%bc%95%e5%87%ba%e7%9a%84%e5%b8%83%e9%9a%86%e8%bf%87%e6%bb%a4%e5%99%a8/

(3)
彭晨涛彭晨涛管理者
上一篇 2020年2月28日
下一篇 2020年2月28日

相关推荐

  • leetcode724-寻找数组的中心索引

    原题 给定一个整数类型的数组nums,请编写一个能够返回数组“中心索引”的方法。 我们是这样定义数组中心索引的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。 如果数…

    算法 2019年11月13日
    0150
  • leetcode8-字符串转换整数 (atoi)

    原题 请你来实现一个 atoi 函数,使其能将字符串转换成整数。 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。接下来的转化规则如下: 如果第一个…

    算法 2020年4月3日
    0280
  • 高效查找的数据结构-HashTree(哈希树)

    本文参考资源: 痴人说Hash - 哈希树 (HashTree)_Java_PinusLee阳光灿烂的生活-CSDN博客 查找——图文翔解HashTree(哈希树)Java菜鸟的自…

    2020年2月17日
    05190
  • leetcode450-删除二叉搜索树中的节点

    原题 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 一…

    2020年1月16日
    0100
  • leetcode21-合并两个有序链表

    原题 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例: 输入: 1->2->4, 1->3->4 输出: 1->1->2->3-…

    2019年12月17日
    0100
  • leetcode27-移除元素

    原题 给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(…

    算法 2019年11月20日
    080
  • Java基础查缺补漏03(附赠哈夫曼树&哈夫曼编码)

    继续我的复习刷题 构造器显式调用父类构造方法的规则 题目: 以下程序的输出结果为 class Base{ public Base(String s){ System.out.pri…

    2020年5月27日
    0250
  • leetcode202-快乐数

    原题 编写一个算法来判断一个数是不是“快乐数”。 一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无…

    算法 2019年12月18日
    0120
  • leetcode112-路径总和

    原题 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。 说明: 叶子节点是指没有子节点的节点。 示例:  给定如下二叉树,…

    算法 2020年1月12日
    0160
  • leetcode704-二分查找

    原题 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 示…

    算法 2019年12月29日
    080

发表回复

登录后才能评论