一看到布隆这个名字想必大多数人都想到了LOL里的英雄布隆,作为一名常玩辅助的玩家,我也喜欢玩布隆。
哈哈哈,言归正传,我们来认识下一另一个布隆。
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
我们可以先假定布隆过滤器就是一个set集合,判断某个元素是否存在这个集合中。
有同学可能会问:那我们直接用HashMap
去检索元素行不行呢,为啥还要用布隆过滤器呢?HashMap 确实可以帮助我们实现这个功能,而且 HashMap 通过一次运算就能确定某个元素的位置,也就可以帮助我们检查某个元素是否在一个集合中。那么我们接下来再思考一个问题:如果这个元素集合中有十亿个随机数,那我们怎样来判断某个数是否存在呢?首先就会带来非常大的空间消耗。
为了解决这个问题,我们的布隆过滤器出现了。
但是由于它是基于概率的,因此它存在一定的误判率,它的Contains()
操作如果返回true
只是表示元素可能存在集合内,返回false
则表示元素一定不存在集合内。因此适合用于能够容忍一定误判元素存在集合内的场景,比如缓存。
它一秒能够进行上百万次操作(主要取决于哈希函数的速度),并且1亿数据在误判率1%的情况下,只需要114MB内存。
布隆过滤器的数据结构是一个位向量,也就是一个由0
、1
所组成的向量(下面是一个初始向量):
每个元素添加进布隆过滤器前,都会经过多个不同的哈希函数,计算出不同的哈希值,然后映射到位向量上,也就是对应的位上面置1:
判断元素是否存在也是如上图流程,根据哈希函数映射的位置,判断所有映射位置是否都为1,如果是则元素可能存在,否则元素一定不存在。
由于不同的值通过哈希函数之后可能会映射到相同的位置,因此如果一个不存在的元素对应的位位置都被其他元素所设置位1,则查询时就会误判:
假设上图元素3334
并没有加入集合,但是由于它映射的位置已经被其他元素所映射,则查询时会误判。
布隆过滤器对元素的删除不太支持,目前有一些变形的特定布隆过滤器支持元素的删除!关于为什么对删除不太支持,其实也非常好理解,hash冲突必然存在,删除肯定是带来连锁反应。
优点:
O(N)
(N位哈希函数的个数,通常情况下比较小)缺点:
布隆过滤器可以告诉我们 “某样东西一定不存在或者可能存在”,也就是说布隆过滤器说这个数不存在则一定不存,布隆过滤器说这个数存在可能不存在,利用这个判断是否存在的特点可以做很多有趣的事情。
定义一个hash函数
/**
* 构建hash函数
*/
public class HashFunction {
// 比特位的大小
private int size;
// hash算法的种子
private int seed;
public HashFunction() {
}
public HashFunction(int size, int seed) {
this.size = size;
this.seed = seed;
}
public int hash(String value) {
int result = 0;
int len = value.length();
// 执行hash算法
for (int i = 0; i < len; i++) {
result = result * seed + value.charAt(i);
}
return (size - 1) & result;
}
}
测试类
import java.util.BitSet;
public class MyBloomFilter {
// 一个长度为10亿的比特位 2^30
private static final int DEFAULT_SIZE = 256 << 22;
// 为了降低错误率,使用假发hash算法,定义一个8个元素的质数数组作为种子
private static final int[] seeds = {3, 5, 7, 11, 13, 31, 37, 61};
// 构建了八个不同的hash算法
private static HashFunction[] functions = new HashFunction[seeds.length];
// 初始化布隆过滤器的bitmap,即位数组
private static BitSet bitSet = new BitSet(DEFAULT_SIZE);
// 添加数据
public static void add(String value) {
if (value != null) {
for (HashFunction f : functions) {
// 计算hash值并修改bitmap中相应位置位true
bitSet.set(f.hash(value), true);
}
}
}
// 判断元素是否存在
public static boolean contains(String value) {
if (value == null)
return false;
boolean ok = true;
for (HashFunction f : functions) {
// 只要有一个hash值不存在就退出循环
if (!bitSet.get(f.hash(value))) {
ok = false;
break;
}
}
return ok;
}
public static void main(String[] args) {
for (int i = 0; i < seeds.length; i++) {
functions[i] = new HashFunction(DEFAULT_SIZE, seeds[i]);
}
// 添加1亿个元素
for (int i = 0; i < 100000000; i++) {
add(String.valueOf(i));
}
String id = "123456789";
add(id);
System.out.println("123456789 是否存在:" + contains(id));
System.out.println("234567890 是否存在:" + contains("234567890"));
}
}
效果还是可以的。
笔记参考:
布隆(Bloom Filter)过滤器——全面讲解,建议收藏