布隆過濾器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 方法存在差異