目录
1、什么是布隆过滤器?
布隆过滤器本质上是一种数据结构,是一种巧妙的概率型数据结构,用来高效的插入和查询,是用来告诉使用者某样东西一定不存在或者可能存在。使用多个哈希函数,将一个数据映射到位图结构中。例:
不了解位图吗?看这篇文章: http://t.csdn.cn/M5vmC
2、实现原理
先来回顾哈希函数吧 ~
2.1、回顾哈希函数
2.1.1、哈希函数概念
将任意的输入数据转换成特定的输出数据的函数,转换后的数据称为哈希值或哈希编码,也叫散列值。如图:
2.1.2、散列函数的基本特性:
- 如果两个散列值是不相同的【同一函数】,那么这两个散列值的原始输入是不同的。这个特性是散列函数具有确定性的结果,具有这种性质的散列函数称为单向散列函数
- 散列函数的输入和输出不是唯一对应的关系,如果两个散列值相同,两个输入值很可能是相通的,但也可能是不同的,这种情况称为“散列碰撞”。
当我们存储海量数据时,哈希的空间效率很低,当只有一个哈希函数时,很容易发生哈希碰撞~
2.2、布隆过滤器数据结构
布隆过滤器是一个由固定大小的二进制向量或者位图和一系列映射函数所组成的。
在初始状态下,对于一个长度为m的位数组,所有位置0,如下:
当有变量被加入集合时,通过K个映射函数将这个变量映射成位图中的K个点,把它们置为1【举例中以3个映射函数为例】:
查询某个变量的时候,只要这些对应的点是否是都是1:
- 如果这些点有一个0,则被查询的变量一定不存在
- 如果都是1,则被查询的变量可能存在
为什么说可能存在,而不是一定存在呢?
那是因为映射函数本身就是散列函数,散列函数就是会有碰撞哒~
3、特点
3.1、支持删除吗?
布隆过滤器不能直接支持删除操作,因为在删除一个元素时,可能会影响其他元素
例:
当我们要删除obj1时,需要将4处置0,而此时obj2的hash3也是映射到4处,就会出现后续的查询有问题~
实现删除方案:
将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素是给计数器加一,删除元素时,减一【通过占用几倍存储空间来增加删除操作】
此方案的缺点:
- 无法确认元素是否真正在布隆过滤器中【误判】
- 存在计数回绕【溢出】
3.2、优点
- 增加和查询元素的时间复杂度为O(k)【k为哈希函数的个数】,与数据量大小无关
- 哈希函数相互之间没有关系,方便硬件并行运算
- 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有较大优势
- 在能够承受一定误判时,布隆过滤器比其他数据结构有很大的空间优势
- 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
- 使用同一组散列函数的布隆过滤器可以进行交、并、差运算
3.3、缺点
- 有误判率,即存在假阳性,即不能准确判断元素是否在集合中【补救:再建立一个白名单,存储可能会误判的数据】
- 不能获取元素本身
- 一般情况,不能从布隆过滤器中删除元素
3.4、误判率
布隆过滤器的误判是指多个输入经过哈希之后在相同的bit位置1了,这样就无法判断究竟是哪个输入产生的,因此误判的根源在于相同的 bit 位被多次映射且置 1。
这种情况也造成了布隆过滤器的删除问题,因为布隆过滤器的每一个 bit 并不是独占的,很有可能多个元素共享了某一位。
4、如何选择哈希函数个数和布隆过滤器长度?
需要使用,套公式即可 ~
5、手动模拟实现布隆过滤器
整体代码:
import java.util.BitSet;
class SimpleHash {
public int cap;//当前容量
public int seed;//随机
public SimpleHash(int cap,int seed) {
this.cap = cap;
this.seed = seed;
}
//模仿库中哈希写哈希函数:
//根据seed的不同,创建不同的哈希函数
int hash(String key) {
int h;
return (key == null) ? 0 : (seed * (cap-1)) & ((h = key.hashCode()) ^ (h >>> 16));
}
}
public class MyBloomFilter {
//导入库中的位图
public BitSet bitSet;
//记录存储了多少数据
public int usedSize;
//随机种子
public static final int[] seeds = {3,5,9,11,15,19,25,31};//这里面的数字是随便设置的
public SimpleHash[] simpleHashes;
public static final int SIZE = 1 << 20;//这个20是随意给的
public MyBloomFilter() {
bitSet = new BitSet(SIZE);
simpleHashes = new SimpleHash[seeds.length];
for(int i = 0;i<simpleHashes.length;i++) {
simpleHashes[i] = new SimpleHash(SIZE,seeds[i]);
}
}
/**
* 添加数据 到布隆过滤器
* @param val
*/
public void add(String val) {
//3个哈希函数,分别处理当前的数据
//把他们都存储在位图中即可
}
/**
* 是否包含val,会存在误判
* @param val
* @return
*/
public boolean contains(String val) {
}
}
补充上述代码空缺部分:
5.1、添加元素
/**
* 添加数据 到布隆过滤器
* @param val
*/
public void add(String val) {
//3个哈希函数,分别处理当前的数据
for (SimpleHash simpleHash : simpleHashes) {
int index = simpleHash.hash(val);
//把他们都存储在位图中即可
bitSet.set(index);
}
usedSize ++;
}
5.2、查询元素
/**
* 是否包含val,会存在误判
* @param val
* @return
*/
public boolean contains(String val) {
for (SimpleHash simpleHash : simpleHashes) {
int index = simpleHash.hash(val);
boolean flg = bitSet.get(index);
if(!flg) {
return false;
}
}
return true;
}
5.3、测试:
//测试
public static void main(String[] args) {
MyBloomFilter myBloomFilter = new MyBloomFilter();
myBloomFilter.add("baidu1");
myBloomFilter.add("baidu2");
myBloomFilter.add("tencent");
System.out.println(myBloomFilter.contains("baidu1"));//true
System.out.println(myBloomFilter.contains("haha"));//false
}
6、guava实现布隆过滤器
- 创建一个maven项目
- 导入依赖
- 测试
依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
测试:
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
public class Test {
private static int size = 1000000;//预计要插入多少数据
private static double fpp = 0.01;//期望的误判率
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);
public static void main(String[] args) {
//插入数据
for (int i = 0; i < 1000000; i++) {
bloomFilter.put(i);
}
int count = 0;
for (int i = 1000000; i < 2000000; i++) {
if (bloomFilter.mightContain(i)) {
count++;
System.out.println(i + "误判了");
}
}
System.out.println("总共的误判数:" + count);
}
}
测试结果:
7、布隆过滤器适用场景
- 网页爬虫中对URL的去重,避免爬取相同的URL地址
- 垃圾邮件过滤,从数十亿个垃圾邮件列表中判断邮箱是否是垃圾邮箱
- 秒杀系统,查看用户是否重复购买
- 数据库防止穿库。 Google Bigtable,HBase 和 Cassandra 以及 Postgresql 使用BloomFilter来减少不存在的行或列的磁盘查找。避免代价高昂的磁盘查找会大大提高数据库查询操作的性能。
- 业务场景中判断用户是否阅读过某视频或文章,比如抖音或头条,当然会导致一定的误判,但不会让用户看到重复的内容。
- 缓存宕机、缓存击穿场景,一般判断用户是否在缓存中,如果在则直接返回结果,不在则查询db,如果来一波冷数据,会导致缓存大量击穿,造成雪崩效应,这时候可以用布隆过滤器当缓存的索引,只有在布隆过滤器中,才去查询缓存,如果没查询到,则穿透到db。如果不在布隆器中,则直接返回。
- WEB拦截器,如果相同请求则拦截,防止重复被攻击。用户第一次请求,将请求参数放入布隆过滤器中,当第二次请求时,先判断请求参数是否被布隆过滤器命中。可以提高缓存命中率。Squid 网页代理缓存服务器在 cache digests 中就使用了布隆过滤器。Google Chrome浏览器使用了布隆过滤器加速安全浏览服务
- Venti 文档存储系统也采用布隆过滤器来检测先前存储的数据。
- SPIN 模型检测器也使用布隆过滤器在大规模验证问题时跟踪可达状态空间。
好啦!!!我们下期再见咯~