布隆过滤器BloomFilter
布隆过滤器
维基百科:
布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。
它实际上是一个很长的二进制向量和一系列随机映射函数。
布隆过滤器可以用于检索一个元素是否在一个集合中。
它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为{\displaystyle O(n),O(\log n),O(1)}{\displaystyle O(n),O(\log n),O(1)}。
布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。
一个Bloom Filter是基于一个m位的位向量(b1,…bm),这些位向量的初始值为0。另外,还有一系列的hash函数(h1,…hk),这些hash函数的值域属于1~m。下图是一个bloom filter插入x,y,z并判断某个值w是否在该数据集的示意图:
上图中,m=18,k=3;插入x是,三个hash函数分别得到蓝线对应的三个值,并将对应的位向量改为1,插入y,z时,类似的,分别将红线,紫线对应的位向量改为1。查找时,当查找x时,三个hash值对应的位向量都为1,因此判断x在此数据集中。y,z也是如此。但是查找w时,w有个hash值对应的位向量为0,因此可以判断不在此集合中。但是,假如w的最后那个hash值比上图中的大1,这是就会认为w在此集合中,而事实上,w可能不在此集合中,因此可能出现误报。显然的,插入数据越多,1的位数越多,误报的概率越大。
类比与Java中HashCode ,HashCode 相等的话Java对象不一定相等,但是HashCode 不同的话则Java对象一定不会相等。借用这种思想,可以帮我们增加判断效率。尤其针对缓存穿透问题
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。在大量缓存失效的情况下可能导致数据库的压力特定时刻剧增引起服务挂的。
JAVA 实现
自定义实现:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.BitSet;
public class SimpleBloomFilter {
private static final Logger log = LoggerFactory.getLogger(SimpleBloomFilter.class);
private int size;
/**
* 增加随机数减少Hash 重复
*/
private int[] seeds;
private BitSet bits;
private SimpleHash[] func;
public SimpleBloomFilter() {
this(new int[]{5, 7, 11, 13, 31, 37, 61}, Integer.MAX_VALUE);
}
public SimpleBloomFilter(int[] seeds, int size) {
this.seeds = seeds;
this.size = size;
this.bits = new BitSet(size);
this.func = new SimpleHash[seeds.length];
init();
}
private void init() {
for (int i = 0; i < seeds.length; i++) {
func[i] = new SimpleHash(this.size, seeds[i]);
}
}
public void add(String value) {
for (SimpleHash f : func) {
bits.set(f.hash(value), true);
}
}
public boolean contains(String value) {
if (value == null) {
return false;
}
boolean ret = true;
for (SimpleHash f : func) {
ret = ret && bits.get(f.hash(value));
}
return ret;
}
public static class SimpleHash {
private int cap;
private int seed;
public SimpleHash(int cap, int seed) {
this.cap = 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 (cap - 1) & result;
}
}
}
Guavar 版
com.google.common.hash.BloomFilter
我是用的版本:
com.google.guava
guava
28.2-android
源码注释
- @param the type of instances that the {@code BloomFilter} accepts
- @author Dimitris Andreou
- @author Kevin Bourrillion
- @since 11.0 (thread-safe since 23.0)
/
@Beta
public final class BloomFilter implements Predicate, Serializable {
@since 11.0 (thread-safe since 23.0) 从11版本开始 但从23版本才是线程安全的
@Betacom.google.common.annotations.Beta表示在将来的发行版中,公共API(公共类,方法或字段)可能会发生不兼容的更改,甚至被删除。带有此注释的API不受其包含库所作的任何兼容性保证。 故不同版本的API 方法存在差异