作用
用于判断一个元素是不是在一个集合里,实际上是一个很长的二进制向量和一系列随机映射函数
优缺点
它的优点是空间效率和查询时间远远高于一般的算法,存储空间和插入/查询时间都是常数,不需要存储元素本身,缺点是有一定的误差,随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣
原理
当一个元素被加入集合时,会进行以下操作
1、通过K个散列函数将这个元素映射成一个位数组中的K个点
2、把K个点的位置设置为1
判断一个元素是否存在集合中,会进行以下操作
1、对该元素进行相同的哈希计算
2、如果得到的值在集合中的每个位置都为1,则说明这个元素可能在过滤器中;如果这些点有任何一个0,则被检元素一定不在
布隆过滤器应用场景
- 判断给定数据是否存在、 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)、邮箱的垃圾邮件过滤、黑名单功能。
- 去重:比如爬给定网址的时候对已经爬取过的 URL 去重
实现一个简单的布隆过滤器
public class Hash {
/**
* 二进制向量数组的大小
*/
private int size;
/**
* 随机数种子
*/
private int seed;
public Hash(int cap,int seed){
this.size=cap;
this.seed=seed;
}
/**
* 计算哈希值,也就是二进制向量计算位置
*/
public int hash(String value){
int result=0;
int len=value.length();
for (int i = 0; i < len; i++) {
result=seed*result+value.charAt(i);
}
return (size-1)&result;
}
}
public class BloomFilter {
/**
* 二进制向量的位数,相当于能存储1000万条url左右,误报率为千万分之一
*/
private static final int BIT_SIZE = 2 << 28;
/**
* 用于生成信息指纹的8个随机数,最好选取质数,hash8次生成8个位置在二进制向量
*/
private static final int[] seeds = new int[]{3, 5, 7, 11, 13, 31, 37, 61};
/**
* 二进制向量大小
*/
private BitSet bits = new BitSet(BIT_SIZE);
/**
* 用于存储8个随机哈希值对象
*/
private Hash[] func = new Hash[seeds.length];
/**
* 构造的时候生成hash种子,对hash存储对象
*/
public BloomFilter() {
for (int i = 0; i < seeds.length; i++) {
func[i] = new Hash(BIT_SIZE, seeds[i]);
}
}
/**
* 向过滤器中添加字符串
*
* @param value
*/
public void addValue(String value) {
/**
* 将字符串value哈希为8个或多个整数,然后在这些整数的bit上变为1
*/
if (value != null) {
for (Hash f : func) {
bits.set(f.hash(value), true);
}
}
}
/**
* 判断字符串是否包含布隆过滤器中
*
* @param value
* @return
*/
public boolean contains(String value) {
if (value == null) {
return false;
}
boolean ret = true;
/**
* 将要比较的字符串计算hash值,再与布隆过滤器比对
*/
for (Hash f : func) {
ret = ret && bits.get(f.hash(value));
}
return ret;
}
public static void main(String[] args) {
//向布隆过滤器中添加值
BloomFilter b = new BloomFilter();
b.addValue("jks");
b.addValue("sdf");
//判断是否存在
System.out.println(b.contains("jks"));
System.out.println(b.contains("as"));
}
}
通过guava 实现的布隆过滤器
引入依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
测试代码
public class BloomFilterUttil {
public static void main(String[] args) {
int num=100000;
/**
* 默认误判率为3%
*/
BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), num);
for(int i=0;i<num;i++){
filter.put(i);
}
int count=0;
/**
* 那不存在的数据进行测试
*/
for (int i = num; i < num + 10000; i++) {
if(filter.mightContain(i)){
count++;
}
}
/**
* 测试结果(一共误判了286个,误判率:0.0286)
*/
System.out.println("一共误判了"+count);
System.out.println("误判率:"+(double)count/10000);
}
}
测试结果的误判率近似3%,根据需求,可以修改误判率
通过 redisson 实现的布隆过滤器
guava 实现的布隆过滤器不错,但是它只能是单机,如果服务器重启,数据也就没了
引入pom
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.11.5</version>
</dependency>
示例代码
RBloomFilter<SomeObject> bloomFilter = redisson.getBloomFilter("sample");
// 初始化布隆过滤器,预计统计元素数量为55000000,期望误差率为0.03
bloomFilter.tryInit(55000000L, 0.03);
bloomFilter.add(new SomeObject("field1Value", "field2Value"));
bloomFilter.add(new SomeObject("field5Value", "field8Value"));
bloomFilter.contains(new SomeObject("field1Value", "field8Value"));