最近作爬蟲項目過濾重複的url的時候,瞭解到一個東西,叫布隆過濾器,而後也學習了一下,寫下這篇博客記錄一下. 下面咱們將分爲幾個專題來介紹布隆過濾器: 1.什麼是布隆過濾器; 2.布隆過濾器的使用場景和缺陷; 3.布隆過濾器java實現; 4.guava中使用布隆過濾器; 5.布隆過濾器的變體java
首先咱們得知道布隆過濾器的概念是什麼,採自wiki百科: 布隆過濾器(英語:Bloom Filter)是1970年由布隆提出的。它其實是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器能夠用於檢索一個元素是否在一個集合中。它的優勢是空間效率和查詢時間都遠遠超過通常的算法,缺點是有必定的誤識別率和刪除困難。 tips:看完這個咱們能夠知道是一個叫布隆的人提出的一個用來檢索一個元素是否在一個集合中的算法,效率高,性能好。git
很長的二進制向量(這裏能夠理解爲很長的bit數組) github
一系列的隨機映射函數(hash函數) 如圖所示,將一個字符串存入布隆過濾器的時候,這個字符串會先被多個hash函數生成不一樣的hash值,而後在對應的bit數組的位置,將0置爲1(bit數組初始化的時候,所有位置都是0);而後第二次在有相同的字符串存入的時候,由於以前已經對應的位置都被置爲1了,因此能夠很輕鬆的知道這個值已經存在了。
舉個栗子,好比第一次將abc@gmail.com存入布隆過濾器,將bit數組的1,3,5位置置爲了1,只要下次再有abc@gmail.com存入布隆過濾器,發現1,3,5已是全是1了,因此可知該字符串已經保存過。(簡單來講就是bit數組中對應的值只要全是1就存在,其餘狀況就是不存在,可是由於存在hash衝突,因此會有誤判,有可能存在abc和xyz的hash值在bit數組中映射的位置是相同的。這種狀況咱們能夠增長班名單,或者調整hash函數來減小誤判狀況。)算法
知道了布隆過濾器的概念,咱們再來看看在實際工做中,它主要使用在哪些地方。數據庫
1.網絡爬蟲能夠經過布隆過濾器判斷當前的url是否已經爬取過;數組
2.防止惡意連接或者垃圾郵件,短信之類的,從數十億個連接或者垃圾郵件中判斷該連接(郵件發件人,短信發信人是不是在黑名單中), 平時手機上來電提示寫着對方式惡意推銷,外賣,這種場景也是能夠用布隆過濾器來判斷;緩存
3.防止緩存擊穿,將已存在的緩存放到布隆中,當使用緩存的時候,能夠先訪問布隆過濾器,存在則訪問緩存,不存在則訪問數據庫;網絡
4.檢索系統查詢當前的輸入信息是否存在於數據庫中,也可使用布隆過濾器。函數
public class BloomFilter {
/** * bitSet的大小 */
private static final int DEFAULT_SIZE = 2 << 24;
/** * 選取的hash函數 */
private static final int[] SEEDS = new int[]{3, 13, 46, 71, 91, 134};
/** * bitSet每一位只能是true或false 其實就是bit數組說的0或者1 */
private BitSet bits = new BitSet(DEFAULT_SIZE);
private SimpleHash[] func = new SimpleHash[SEEDS.length];
public static void main(String[] args) {
String value = "wxwwt@gmail.com";
BloomFilter filter = new BloomFilter();
System.out.println(filter.contains(value));
filter.add(value);
System.out.println(filter.contains(value));
}
public BloomFilter() {
for (int i = 0; i < SEEDS.length; i++) {
func[i] = new SimpleHash(DEFAULT_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;
}
/** * 計算hash值 * * @param value * @return */
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;
}
}
}
複製代碼
google的java工具包中已經編寫了布隆過濾器的代碼,能夠直接拿來用,具體使用能夠google一下,這裏只簡單的提一下:工具
public static void main(String[] args) {
// 建立布隆過濾器
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), 1000);
// 添加數據
for (int index = 0; index < 100000; index++) {
bloomFilter.put("wxwwt-" + index);
}
// 查看數據是否存在
if (bloomFilter.mightContain("wxwwt-" + 9999)) {
System.out.println("存在");
}
// 誤判元素
if (bloomFilter.mightContain("不存在的元素")) {
System.out.println("誤判啦");
} else {
System.out.println("不存在");
}
}
複製代碼
運行結果:
在數據量很大的時候使用布隆過濾器很是方便,佔用的內存空間很小(由於使用的是bit數組,空間使用很是小,空間開銷就是bit數組的大小),查詢效率也很高(直接經過計算hash函數的出來的),惟一的問題就是可能會有誤判,不過幾率也是比較小的,也能夠經過增長白名單和增長hash函數的數量來減小這個問題的產生,總的來講利大於弊,在僅判斷元素是否存在而不涉及到刪除的狀況下很是好用(最基本的bloomfilter是沒法刪除元素的,置爲0就無法判斷存在狀況了,有bloom過濾器的變體是支持刪除的)。
1.nick-weixx.github.io/2018/03/01/…
2.zh.wikipedia.org/wiki/布隆過濾器
3.zhangluncong.com/2018/05/23/…