场景
- 爬虫时判断某个URL是否已经被爬取过
- 黑名单过滤
- 防止缓存穿透
- …
实现原理
定义一个长度为m的bit型数组flag[] (用来添加元素以及判断元素是否存在,因为Integer的最大值为2147483647,所以m取该值即可)
定义n个不同的hash函数(在添加元素时,需要设置flag[]哪些位为1; 在判断元素是否存在时,需要取flag[]哪些位来判断)
添加某个元素时,通过n个hash函数算出该元素的n个hash值(整型值),把flag[]对应的位置1
判断某个元素是否存在时,通过n个hash函数算出该元素的n个hash值(整型值),在flag[]取出对应的值,只要有一个不为1 ,即可判断为不存在.否则就任务元素存在
布隆过滤器优缺点
优点: 大大节省空间
场景: 在10亿数据中判断某个数据是否存在
如果使用HashSet/HashMap来实现的话
查找的时间复杂度是O(1),但是我们来算一下存储空间,Hash值为Integer类型,占四个字节,那10亿条数据占用的空间就是:10亿*4/1024/1024/1024约等于3.7G…这个实现方案很明显不现实
如果使用布隆过滤器实现
占用的空间大约为2147483647/8/1024/1024=256M
缺点: 误差
由上面的分析可知, hash函数是存在hash冲突的, 所以布隆过滤器是会有误判的情况.
表现为:
如果某条记录被判断为不存在,则该记录必然不存在
如果某条记录被判断存在,则该记录可能会不存在
代码实现
JDK版
public class BloomFilterDemo {
private static final int insertions = 1000000;
@Test
public void bfTest1(){
//初始化一个存储string数据的布隆过滤器,初始化大小100w,不能设置为0
BloomFilter<String> bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), insertions,0.001);
//初始化一个存储string数据的set,初始化大小100w
Set<String> sets = new HashSet<>(insertions);
//初始化一个存储string数据的set,初始化大小100w
List<String> lists = new ArrayList<>(insertions);
//向三个容器初始化100万个随机并且唯一的字符串---初始化操作
for (int i = 0; i < insertions; i++) {
String uuid = UUID.randomUUID().toString();
bf.put(uuid);
sets.add(uuid);
lists.add(uuid);
}
//布隆过滤器错误判断的次数
int wrong = 0;
//布隆过滤器正确判断的次数
int right = 0;
for (int i = 0; i < 10000; i++) {
//按照一定比例选择bf中肯定存在的字符串
String test = i%100==0?lists.get(i/100):UUID.randomUUID().toString();
if(bf.mightContain(test)){
if(sets.contains(test)){
right ++;
}else{
wrong ++;
}
}
}
//100
System.out.println("=================right=====