1.什么是布隆过滤器?
- 布隆过滤器(Bloom Filter)是一个叫做 Bloom 的老哥于1970年提出的。
- 实际上可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。
- 它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
2.布隆过滤器的原理介绍
- 图解:
-
如图所示,布隆过滤器添加元素时,该元素首先由多个哈希函数生成不同的哈希值,然后在对应的位数组的下表的元素设置为 1(当位数组初始化时 ,所有位置均为0)。当第二次存储相同字符串时,因为先前的对应位置已设置为1,所以很容易知道此值已经存在。
-
如果我们需要判断某个元素是否在布隆过滤器中时,只需要对给定字符串再次进行相同的哈希计算,得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。
-
不同的字符串可能哈希出来的位置相同,这种情况我们可以适当增加位数组大小或者调整我们的哈希函数。
-
所以,布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。
3.布隆过滤器使用场景
- 判断给定数据是否存在。
- 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)等等、邮箱的垃圾邮件过滤、黑名单功能等等。。
4.用Java 实现布隆过滤器
- 下面是我参考网上已有代码改的:
/**
* 布隆过滤器
*
* @author wangjie
* @version V1.0
* @date 2020/5/11
*/
public class BloomFilterDemo {
/**
* 位数组的大小
*/
private static int SIZE;
/**
* 通过这个数组可以创建不同的哈希函数
*/
private static int[] SEEDS;
/**
* 位数组。数组中的元素只能是 0 或者 1
*/
private BitSet bits;
/**
* 存放包含 hash 函数的类的数组
*/
private SimpleHash[] func;
/**
* 误判率
*/
private MisjudgmentRate rate;
/**
* 自动清空
*/
private Double autoClearRate;
/**
* 使用数量
*/
private final AtomicInteger useCount = new AtomicInteger(0);
/**
* 静态内部类。hash 函数
*/
public static class SimpleHash {
private int cap;
private int seed;
public SimpleHash(int cap, int seed) {
this.cap = cap;
this.seed = seed;
}
/**
* 计算 hash 值
*/
public int hash(Object value) {
int h;
return (value == null) ? 0 : Math.abs(seed * (cap - 1) & ((h = value.hashCode()) ^ (h >>> 16)));
}
}
/**
* 误判率
*/
public enum MisjudgmentRate {
/**
* 每个字符串分配4个位
*/
VERY_SMALL(new int[] { 2, 3, 5, 7 }),
/**
* 每个字符串分配8个位
*/
SMALL(new int[] { 2, 3, 5, 7, 11, 13, 17, 19 }),
/**
* 每个字符串分配16个位
*/
MIDDLE(new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 }),
/**
* 每个字符串分配32个位
*/
HIGH(new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
101, 103, 107, 109, 113, 127, 131 });
private int[] seeds;
private MisjudgmentRate(int[] seeds) {
this.seeds = seeds;
}
public int[] getSeeds() {
return seeds;
}
public void setSeeds(int[] seeds) {
this.seeds = seeds;
}
}
/**
* 默认中等程序的误判率
* @param dataCount 预期处理的数据规模,如预期用于处理1百万数据的查重,这里则填写1000000
*/
public BloomFilterDemo(int dataCount){
this(MisjudgmentRate.MIDDLE, dataCount, null);
}
/**
*
* @param rate 枚举类型的误判率
* @param dataCount 预期处理的数据规模,如预期用于处理1百万数据的查重,这里则填写1000000
* @param autoClearRate 自动清空过滤器内部信息的使用比率
*/
public BloomFilterDemo(MisjudgmentRate rate, int dataCount, Double autoClearRate){
long bitSize = rate.seeds.length * dataCount;
if (bitSize < 0 || bitSize > Integer.MAX_VALUE) {
throw new RuntimeException("位数太大溢出了,请降低误判率或者降低数据大小");
}
this.rate = rate;
SEEDS = rate.seeds;
SIZE = (int) bitSize;
func = new SimpleHash[SEEDS.length];
for (int i = 0; i < SEEDS.length; i++) {
func[i] = new SimpleHash(SIZE, SEEDS[i]);
}
bits = new BitSet(SIZE);
this.autoClearRate = autoClearRate;
}
/**
* 添加元素到位数组
*/
public void add(Object value) {
checkNeedClear();
if(!contains(value)){
for (SimpleHash f : func) {
bits.set(f.hash(value), true);
}
useCount.getAndIncrement();
}
}
/**
* 判断指定元素是否存在于位数组
*/
public boolean contains(Object value) {
boolean ret = true;
for (SimpleHash f : func) {
ret = ret && bits.get(f.hash(value));
}
return ret;
}
/**
* 检查是否需要清空
*/
private void checkNeedClear() {
if (autoClearRate != null) {
if (getUseRate() >= autoClearRate) {
synchronized (this) {
if (getUseRate() >= autoClearRate) {
bits.clear();
useCount.set(0);
}
}
}
}
}
public double getUseRate() {
return (double) useCount.intValue() / (double) SIZE;
}
public static void main(String[] args) {
String value1 = "fffdfg";
String value2 = "ddbgbfgbfbbgfb";
BloomFilterDemo filter = new BloomFilterDemo(2<<24);
System.out.println(filter.contains(value1));
System.out.println(filter.contains(value2));
filter.add(value1);
filter.add(value2);
System.out.println(filter.contains(value1));
System.out.println(filter.contains(value2));
Integer value11 = 13423;
Integer value21 = 22131;
System.out.println(filter.contains(value11));
System.out.println(filter.contains(value21));
filter.add(value11);
filter.add(value21);
System.out.println(filter.contains(value11));
System.out.println(filter.contains(value21));
}
}
- 运行结果:
false
false
true
true
false
false
true
true