一.简介
布隆过滤器(英语:Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制数组(00000000)+一系列随机hash算法映射函数,主要用于判断一个元素是否在集合中。通常我们会遇到很多要判断一个元素是否在某个集合中的业务场景,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、哈希表等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间也会呈现线性增长,最终达到瓶颈。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为O(n),O(logn),O(1)。这个时候,布隆过滤器(Bloom Filter)就应运而生
二.应用场景
1.黑白名单校验
2.解决Redis缓存穿透。
3.在爬虫时,对爬虫网址进行过滤,已经存在布隆中的网址,不在爬取。
4.垃圾邮件过滤,对每一个发送邮件的地址进行判断是否在布隆的黑名单中,如果在就判断为垃圾邮件。
5.大量数据时,判断给定的数据是否在其中。
三.原理
计算公式
四. 布隆过滤器为什么不能删除
布隆过滤器的误判是指多个输入经过哈希之后在相同的bit位置1了,这样就无法判断究竟是哪个输入产生的,因此误判的根源在于相同的 bit 位被多次映射且置 1。这种情况也造成了布隆过滤器的删除问题,因为布隆过滤器的每一个 bit 并不是独占的,很有可能多个元素共享了某一位。如果我们直接删除这一位的话,会影响其他的元素,布隆过滤器可以添加元素,但是不能删除元素。因为删掉元素会导致误判率增加。
五.特性
1.判断元素存在时有误差:存在表示可能存在,不存在则表示这个元素一定不存在
2.使用的时候尽量不要让实际元素的数量大于初始化布隆过滤器的数量,减少布隆过滤器的扩容
六.手写布隆过滤器
package com.roar.edr.utils;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class BloomUtil {
private static int capacity = 100000000;
private static double errorP = 0.03;
private static int[] seeds = {6, 18, 64, 89, 126, 189, 223};
private static double m;
private static double k;
private static double p;
private static double mem;
private static byte[] bytes;
public static void init(){
m = Math.floor(-(capacity * Math.log(errorP)) / Math.pow(Math.log(2), 2));
k = Math.floor(Math.log(2) * m / capacity);
p = Math.pow(1 - (-Math.exp(-Math.log(2))), k);
mem = m / 8 / 1024 / 1024;
bytes = new byte[(int) m];
System.out.println("m = " + m);
System.out.println("k = " + k);
System.out.println("p = " + p);
System.out.println("mem = " + mem);
}
public static List<Integer> getHashes(String value){
List<Integer> hashArray = new ArrayList<>((int) k);
Random random = new Random(seeds.length);
for (int i = 0; i < k; i++) {
int nextInt = random.nextInt();
int i1 = value.hashCode() ^ (value.hashCode() >>> 16);
if (!hashArray.contains((Math.abs(nextInt * (int) m- 1) & i1) % (int) m)){
hashArray.add((Math.abs(nextInt * (int) m- 1) & i1) % (int) m);
}
}
return hashArray;
}
public static void add(String value){
List<Integer> hashes = getHashes(value);
for (Integer hash : hashes) {
bytes[hash] = 1;
}
}
public static boolean mightContains(String value){
List<Integer> hashes = getHashes(value);
boolean exists = true;
for (Integer hash : hashes) {
if (bytes[hash] == 0){
exists = false;
break;
}
}
return exists;
}
public static void main(String[] args) {
int _1W = 10000;
init();
// 初始化布隆过滤器,设计预计元素数量为100_0000L,误差率为1%
BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 100 * _1W, 0.03);
int n = 100 * _1W;
for (int i = 0; i < n; i++) {
bloomFilter.put(String.valueOf(i));
}
for (int i = 0; i < n; i++) {
System.out.println("getHashes(String.valueOf(i)) = " + getHashes(String.valueOf(i)));
add(String.valueOf(i));
}
int count = 0;
int count1 = 0;
for (int i = n + 1; i <= n + 100000; i++) {
if (bloomFilter.mightContain(String.valueOf(i))){
count ++;
}
}
for (int i = n + 1; i <= n + 100000; i++) {
if (mightContains(String.valueOf(i))){
count1 ++;
}
}
System.out.println("count1 = " + count1);
System.out.println("count = " + count);
System.out.println("过滤器误判率:" + 1.0 * count / n * 100);
System.out.println("过滤器误判率:" + 1.0 * count1 / n * 100);
}
}