布隆过滤器(Bloom Filter)

如果觉得对你有帮助,能否点个赞或关个注,以示鼓励笔者呢?!博客目录 | 先点这里

  • 前提概念

    • 什么是布隆过滤器?
    • 布隆过滤器的作用?
    • 布隆过滤器的缺陷
  • 数据结构
    • 关键因子
    • 结构设计
  • 代码实现
    • 结构体
    • 因子计算
    • 哈希函数
    • 插入元素
    • 是否存在
  • 问题
    • 布隆过滤器可以节省多少空间?

前提概念


什么是布隆过滤器?

布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难

  • 布隆过滤器 -@维基百科

布隆过滤器的作用?

布隆过滤器有什么作用呢?通俗的讲,布隆过滤器的主要作用就是 "以更低的空间效率检索一个元素是否存在其中"

所以我们知道布隆过滤器的好处就是省空间,作用就是可以判断一个元素是否存在,那么结合实际的应用场景,通常布隆过滤器有哪些应用场景呢?

  • 推荐去重过滤

    • 比如对一个短视频推荐系统而言,在视频 feed 流场景,需求是向用户推荐了指定内容后,一段时间内不再重复出现已推荐过的内容,那么布隆过滤器就可以大派用场。
  • 缓存穿透过滤
    • 比如在互联网大并发场景下,为防止流量大面积穿透压垮 RDS,很多系统都会结合使用缓存去抗流量。所以我们也可以考虑使用布隆过滤器,针对识别出的外部非法请求,或是空结果请求,通通记录在过滤器中,并在下次访问中,提前由过滤器先行过滤
  • 黑名单系统
    • 比如某些系统需要记录百万,千万甚至更多的黑名单字符,使用传统的哈希表实现,需要使用到较大的存储空间,那么我们也可以采用布隆过滤器去提供相同的能力,并有效降低空间存储成本

布隆过滤器的弊端

我们都知道布隆过滤器相比传统 “检索元素是否存在” 的容器而言,具有节省空间存储成本的优势,在大数据领域则尤其明显。那么它就只有长处,毫无缺陷吗?

非也,布隆过滤器还有具有两个非常重要的缺陷

  • 具有一定概率的误判率,即假阳性率 fpp
  • 不具备删除元素的能力

具有一定的误判率是什么意思呢?意思就是可能会将 “不存在的元素误判为存在” ,而这个概率就是 fpp , 如何规避和解决?

  • 首先必然存在误判,如无法接受误判,则布隆过滤器不适合使用。但可以根据业务需求降低概率,或是选择我们可接受的误判概率;
  • 误判率 (假阳性率) 大小会受到哈希函数的个数,位数组的大小,以及预期元素个数等因素的影响
  • 通常根据业务需求,选择可接受的 fpp 构建布隆过滤器,比如 fpp = 0.001, 每 1000 次误判 1 次

另外不具备删除元素的能力又是什么意思呢?根据布隆过滤器的设计,一个元素会被多个哈希函数计算,并得到多个不同的位置;因为是基于哈希实现,那么必然存在哈希冲突,即多个元素的某些哈希函数的哈希值是可能一致,即多个元素会共用一些位置。如果我们要删除某个元素,那么就需要将其所占有的位置重置为 0,但这可能会影响到其它共有该位置的元素的判断,所以综上布隆过滤器是不支持删除的。如果要删除,要怎么解决呢?

布隆过滤器无法解决,但是可以基于布隆过滤器进行扩展,衍生出可删除的过滤器, 比如 CBF (counting bloom filter)Cuckoo Filter (布谷鸟过滤器)等, 如果要删除,则布隆过滤器的基本构型是不满足需求的,可以采用其他类似的数据结构

数据结构


关键因子

  • bloom filter calculate
  • n 是过滤器预期支持的元素个数
  • m是过滤器位数组的大小,即该过滤器总共占用多少 bit 的空间
  • c是每个元素平均占用的空间
  • p是假阳性概率,即 fpp (false positive probability)
  • k哈希函数的个数

计算公式

  • m=−nlog(p)1log(2)2m = -nlog(p)\frac{1}{log(2)^2}m=−nlog(p)log(2)21​
  • c=mnc = \frac{m}{n}c=nm​
  • p=(1−e−knm)k=(1−e−kmn)k=(1−e−kc)kp = (1-e^{\frac{-kn}{m}})^k = (1-e^{\frac{-k}{\frac{m}{n}}})^k = (1 - e^{\frac{-k}{c}})^kp=(1−em−kn​)k=(1−enm​−k​)k=(1−ec−k​)k
  • k=mn∗ln2=0.7mn=0.7ck = \frac{m}{n} *ln2 = 0.7\frac{m}{n} = 0.7ck=nm​∗ln2=0.7nm​=0.7c

通常创建一个布隆过滤器,会需要用户提供想支持的 n 和预期的 p, 然后通过公式得到其余最佳的参数


结构设计

布隆过滤器的数据结构其实也挺简单,如果之前有了解过位图的话,那么就更简单了。整个布隆过滤器的底层实现就是基于 “位数组 + 哈希函数” 实现的

假设某个布隆过滤器的位数组大小为 1000 bits, 即 1000 个位。由 3 个 hash functions 组成


当我们往该布隆过滤器传入一个元素 (Hello World) ,那么该元素就会经过 3 个 hash functions 计算,得到 3 个 hash 值,最后哈希值经过模运算映射到位数组的具体第 x 位上,并标记为 1

当我们要判断某个元素是否在布隆过滤器时,同理,也是将该元素传入,经过哈希函数计算,并得到映射位数组的索引值,判断这个几个索引的位是否为 1, 如果都为 1 则代表该元素已经存在了,但只要有一个位置不为 1,我们就认为该元素并不存在

代码实现


结构体

布隆过滤器结构主要可以由三部分构成,关键因子 (n,m,p,k…)哈希函数位数组,所以假设我们要实现一个布隆过滤器,首先就要将以上三部分内容构建好,这里写一份伪代码,基本可以简单的体现出布隆过滤器的结构设计

public abstract class BloomFilter {// 字节数组byte[] bytes;// 关键因子int n;double p;int m;int k;// 哈希函数List<Hash> hashes;// 插入元素public void put(byte[] bs);// 是否存在public boolean contains(byte[] bs);
}
  • 为什么 n, m 是 int, 而不是 long, 因为 Java 中没有位数组的概念,所以我们就使用字节数组去实现位数组,而数组大小的最大值是 Integer.MAX_VALUE, 所以 n,m 也采用 int
  • 其实也并非没有位数组的概念,可以通过 Java 自带的 BitSet 去实现,会更简单,但是这里的目的是为了学习,所以就不直接使用轮子了。除此之外,还要很多实践中,会使用 long 数组去实现

因子计算

  • 通过 (n,p) 计算最佳的 m
   /*** Computes m (total bits of Bloom filter)*/public static int optimalNumOfBits(int n, double p) {if (p == 0) {p = Double.MIN_VALUE;}return (int) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));}
  • 通过 (n,m) 计算最佳的 k
    /*** Computes the optimal k */public static int optimalNumOfHashFunctions(int n, int m) {// (m / n) * log(2), but avoid truncation due to division!return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));}
  • 通过 (n,m,k) 计算最佳的 p
    /*** Computes the false positive probability* 1. p = (1 - e ^ (-kn/m))^k*/public static double optimalFpp(int n, int m, int k) {// keep 5 digits after the decimal pointint point = 100000;return (double) Math.round((point * Math.pow(1 - Math.exp((double) (-k * n) / m), k))) / point;}
  • 通过 (n,m) 计算 c
  /*** Bits occupied each element*/public static double bitsOfElement(int n, int m) {return (double) m / n;}
  • 通常我们可以通过用户传递的 n 和 p,构造出一个预期的布隆过滤器

哈希函数

哈希函数可以采用各种混合的哈希算法去搭配,我这件为了简单实现,建议采用 murmur3 hash, 通过加不同的 seed 盐去实现不同的哈希效果

 // 成员变量 k 个哈希函数private List<Hash> hashes;// 获得 k 个索引值public int[] hashes(byte[] bs) {int[] indexs = new int[hashes.size()];for (int i = 0; i < hashes.size(); i++) {// int index = (int) ((hashes.get(i).hashToLong(bs) & Long.MAX_VALUE) % this.m);indexs[i] = index;}return indexs;}// 哈希接口public interface Hash {long hashToLong(byte[] bs);}

这里是伪代码,Hash 接口没有实现,可以采用 murmur3 加盐实现多个哈希函数

  • hashToLong 就是传入数据,获得一个 64 位的哈希值

    • 为什么要再 & Long.MAX_VALUE 呢?目的是为了让 hash 值是一个正数,(与 01111... 相与,自然就是正数啦)
  • 为什么要 % m, 就是布隆过滤器的总的位数, 通过 hash 值求得在过滤器中的位置

插入元素

    // 插入元素&是否存在public synchronized void put(byte[] bs) {int[] indexs = hashes(bs);for (int index : indexs) {// 所在的具体字节byte bits = (byte) (index / B);// 所在的具体位byte offset = (byte) (1 << (B_MASK - (index % B)));bytes[index / B] = (byte) (bits | offset);}}
  • B = 8, 代表一个字节 8 位; B_MASK 是掩码, B_MASK = B - 1,为了做取模运算
  • 首先得到 index, 通过 index / B 得到 index 所在那个 byte
  • 其次通过 index % B 得到所在该 byte 的具体位索引,比如等于 2, (00100000, 一个 byte 8 位,1 所在的位置就是 2)
  • 通过 index % B 知道了具体的位索引后,为了将该位置变成 1,那么我们就需要得到位加法的偏移量,即得到 00100000 去做加法
  • 怎么得到那?即把 00000001 左移 B_MASK - (index % B) = 7 - 2 = 5 位,左移 5 位后,得到 00100000
  • 假设所在字节为 10000001 , 我们要将索引为 2 的位置变为 1, 则两个 byte 做或运算,即位加法即可 10000001 | 00100000 = 10100001
  • 最后覆盖 index 所在的位即可

是否存在

 // 是否存在public synchronized boolean contains(byte[] bs) {int[] indexs = hashes(bs);for (int index : indexs) {byte bits = (byte) (index / B);byte offset = (byte) (1 << (B_MASK - (index % B)));if ((bits & offset) == 0) {return false;}}return true;}
  • 是否存在的位运算原理跟插入元素是一致的,直接看插入即可
  • 需要区分的是因为有多个哈希函数,自然就有多个 index, 只要有一个 index 不为 1,我们就认为其不存在

伪代码框架

要实现一个完整的布隆过滤器其实也不难,我这里附上一份简化的布隆过滤器伪代码,基本上除了哈希函数的选型留空,其余逻辑都有基本的实现,可以简易的参考,配合理解学习

/*** @author SnailMann*/
public abstract class BloomFilter {// A byte has 8 bitsprivate static final int B = 8;// B maskprivate static final int B_MASK = B - 1;// 字节数组private byte[] bytes;// 关键因子private int n;private int p;private int m;private int k;List<Hash> hashes;public BloomFilter(int n, int p) {this.n = n;this.p = p;this.m = optimalNumOfBits(n, p);this.k = optimalNumOfHashFunctions(this.n, this.m);this.bytes = new byte[this.m];}// 插入元素&是否存在public synchronized void put(byte[] bs) {int[] indexs = hashes(bs);for (int index : indexs) {// 所在的具体字节byte bits = (byte) (index / B);// 所在的具体位byte offset = (byte) (1 << (B_MASK - (index % B)));bytes[index / B] = (byte) (bits | offset);}}public synchronized boolean contains(byte[] bs) {int[] indexs = hashes(bs);for (int index : indexs) {byte bits = (byte) (index / B);byte offset = (byte) (1 << (B_MASK - (index % B)));if ((bits & offset) == 0) {return false;}}return true;}public int[] hashes(byte[] bs) {int[] indexs = new int[hashes.size()];for (int i = 0; i < hashes.size(); i++) {int index = (int) ((hashes.get(i).hashToLong(bs) & Long.MAX_VALUE) % this.m);indexs[i] = index;}return indexs;}private static int optimalNumOfHashFunctions(int n, int m) {return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));}private static int optimalNumOfBits(int n, double p) {if (p == 0) {p = Double.MIN_VALUE;}return (int) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));}public interface Hash {long hashToLong(byte[] bs);}}

如果需要完整可用的实现,可以参考博主之前实现比较完善的代码

  • 支持 LRU 的布隆过滤器 - @ SnailMann

问题


布隆过滤器可以节省多少空间?

我们都知道布隆过滤器可以大大节省空间,那么它到底可以节省多少的空间呢?**这要如何去计算呢?**在我们学习了关键因子的计算后,这简直就是一件小事情

我们知道 c = m/nc 代表每个元素在布隆过滤器中平均所占用的空间,即多少的 bits。我们假设构建一个满足 p = 0.001, n = 10000 条件的 Filter

经过计算可以知道 m = 143776, k = 10, 那么平均每个元素所占用的空间就是 c = m/n = 14.38 bits。通常主键的类型会是是 32/64 整型或是字符串

  • 假设我们要判断的元素是 64 位的整型,一个 64 位的数值占 64 bits, 所以每个元素我们就压缩了近 (64-14.38/64) = 63% 的空间
  • 假设我们要判断的元素是 32 长度的字符串,而每个字符串就占用 32 * 8 = 256 bits, 那么每个元素我们就将近压缩了 94% 的空间

这是单个元素空间压缩的计算方式,如果觉得不够直观,那我们可以按照总量来计算, 假设 n = 100000000 (1 亿), p = 0.001, 保持 c = 14.38 ,那么 m 就会是1437758757 bits (171.39MB)。假设依然是 32 长度的字符串,那么要存储 1 亿个这样的字符,我们就需要 256 * 100000000 = 25,600,000,000 = 2.98 GB 的空间大小, 而如果我们采用布隆过滤器,则只需要 171 MB

如果你觉得这空间压缩也不过如此,那么就将数据量再放大点,加个 10000 倍,是不是就可以闻到

【数据结构】初入数据结构之布隆过滤器(Bloom Filter)及实现相关推荐

  1. mysql布隆过滤器源码_布隆过滤器(Bloom Filter)的原理和实现

    什么情况下需要布隆过滤器? 先来看几个比较常见的例子 字处理软件中,需要检查一个英语单词是否拼写正确 在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上 在网络爬虫里,一个网址是否被访问过 yahoo, ...

  2. 布隆过滤器+布隆过滤器(Bloom Filter)详解

    布隆过滤器+布隆过滤器(Bloom Filter)详解 程序 = 数据结构 + 算法 -- 图灵奖得主,计算机科学家N.Wirth(沃斯) A Bloom filter is a space effi ...

  3. js 数组 实现 完全树_Flink实例(六十八):布隆过滤器(Bloom Filter)的原理和实现 - 秋华...

    什么情况下需要布隆过滤器? 先来看几个比较常见的例子 字处理软件中,需要检查一个英语单词是否拼写正确 在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上 在网络爬虫里,一个网址是否被访问过 yahoo, ...

  4. 布隆过滤器速度_布隆过滤器(Bloom Filter)详解

    布隆过滤器[1](Bloom Filter)是由布隆(Burton Howard Bloom)在1970年提出的.它实际上是由一个很长的二进制向量和一系列随机映射函数组成,布隆过滤器可以用于检索一个元 ...

  5. Redis缓存穿透“新杀招“:布隆过滤器Bloom Filter

    场景分析 这篇文章来讲述缓存穿透的补充解决方案. 为什么要用补充来形容呢? 在之前的文章中,我们提到缓存穿透的解决方案时,我是这么说的: 关于缓存穿透,我们可以在用户访问数据库后将null值存入Red ...

  6. 布隆过滤器 Bloom Filter

    目录 一 前言 二 布隆过滤器 三 Redis实现 四 Guava实现 五 扩展知识点 一 前言 假如有一个15亿用户的系统,每天有几亿用户访问系统,要如何快速判断是否为系统中的用户呢? 方法一,将1 ...

  7. 布隆过滤器(Bloom Filter)详解——基于多hash的概率查找思想

    转自:http://www.cnblogs.com/haippy/archive/2012/07/13/2590351.html   布隆过滤器[1](Bloom Filter)是由布隆(Burton ...

  8. 布隆过滤器(Bloom Filter)原理及优缺点剖析

    直观的说,bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中. 和一般的hash set不同的是,这个算法无需存储key的值,对于每个key,只需要k个比特位,每个存储 ...

  9. 布隆过滤器Bloom Filter简介

    背景: 如果在平时我们要判断一个元素是否在一个集合中,通常会采用查找比较的方法,下面分析不同的数据结构查找效率: 采用线性表存储,查找时间复杂度为O(N) 采用平衡二叉排序树(AVL.红黑树)存储,查 ...

  10. 布隆过滤器(Bloom Filter)算法

    布隆过滤器原理 开发一个电商项目,因为数据量一直在增加(已达亿级),所以需要重构之前开发好的秒杀功能,为了更好的支持高并发,在验证用户是否重复购买的环节,就考虑用布隆过滤器. 也顺便更加深入的去了解下 ...

最新文章

  1. MSSQL扫盲系列(4)-系统函数
  2. 列举一些RNN类模型的常见使用形式以及常见的应用
  3. php 删除子字符串函数,PHP删除字符串中的任何字符函数
  4. 一次单核CPU占用过高问题的处理
  5. 大棚骨架搭建好 科学施肥增收增产
  6. 汇编语言位向量(位映射)
  7. mysql 测试快生产慢_生产上MySQL慢查询优化实战,SQL优化实战
  8. GNU make manual 翻译(七十三)
  9. CentOS6.4 Install FTP
  10. React开发(210):react中try...catch..
  11. 为何只能在其关联的线程内启动timer?
  12. java 顺序表的实现_顺序表的简单实现(Java)
  13. alexa工具条下载_如何聆听(和删除)您给Alexa的每条命令
  14. (十一)【数电】(组合逻辑电路)数据分配器和数据选择器
  15. NoSuchMethodError: No virtual method(Ljava/io/File;)V in class Landroid/media/MediaRec
  16. 利用继电器实现防抖自锁功能
  17. a1708硬盘转接口_macbook pro2017 a1708转接卡更换大容量硬盘,Apple/苹果笔记本电脑_好文分享_优购 - 荐优,购适...
  18. 10个值得珍藏的4K高清壁纸网站推荐
  19. 习题6-8 单词首字母大写 (15 分)
  20. 如何通过手机APP远程控制PLC

热门文章

  1. 电子线路基础 18 -----第 18 讲笔记
  2. VR智慧城市运营服务平台
  3. 【转】用什么软件做音乐
  4. java函数接口和方法引用
  5. CreateToolhelp32Snapshot枚举进程
  6. 产品读书《魔鬼经济学2:拥有清晰思维的艺术》
  7. 嵌入式c语言开发闹钟,嵌入式电子闹钟()时钟课程设计.doc
  8. 简易单片机-电子定时闹钟程序设计笔记 长按按钮-定时器 蜂鸣器 LED
  9. python商品监控系统_Python写的一个简单监控系统
  10. AD9361结构及功能解析