大資料排重演算法-布隆演算法(BloomFilter)
阿新 • • 發佈:2019-01-27
前續:網頁上已經有很多布隆過濾器很全的資料了,由於博主最近在做網頁爬蟲,遇到url防重問題,所以認真分析了布隆濾波器原理,也參考了相關博文。旨在給出不同人對其不同的理解,好給大家更全面的參考。
BloomFilter演算法,是一種大資料排重演算法。在一個數據量很大的集合裡,能準確斷定一個物件不在集合裡;判斷一個物件有可能在集合裡,而且佔用的空間不大。它不適合那種要求準確率很高的情況,零錯誤的場景。通過犧牲部分準確率達到高效利用空間的目的。
場景一:假如有一個很大的表,通過欄位key查詢資料,操作很重;業務方請求時,傳過來的key有很大一部分是不存在的;這種不存在的key請求就會浪費我們的查詢資源。針對這種情況,我們可以引人BloomFilter演算法,在請求key查詢之前,使用BloomFilter匹配。如果不存在,就不用去查詢了(正確率百分之百);如果存在,走原來的查詢流程(有可能不存在的key混進去了)。 場景二:假如有一個很大的表,通過欄位key判斷是否存在,操作很重,如果存在就做一些操作,不存在就加入表中;可容許一定的誤判。對應這種情況,我們也可以引入BloomFilter演算法,通過key查詢表判斷存在否的方式可換成BloomFilter演算法。如果存在,我們執行以前的邏輯(有一定的誤判,業務也允許一定的錯誤);如果不存在,也執行以前的邏輯。 BloomFilter是由一個長度為n的bit陣列S和k個hash演算法組成。先使bit陣列的初始值為0. 新增值M:M經過k個hash演算法計算後,得到:M1, M2 … Mk; 然後,使S[M1]=1,S[M2]=2... S[Mk]=1 判斷值Y:Y經過k個hash演算法計算後,得到:Y1,Y2... Yk。 然後,判斷S[Y1],S[Y2] … S[Yk] 是否都為1。如果有一個不為1,那這個Y就一定是不存在的,以前沒新增過;如果都為1,那這個Y可能存在,也可能其他值新增後,影響了這次判斷的結果。 我們要做的是儘量降低正確判斷的誤判率,資料顯示, 當 k = ln(2)* m/n 時(k是hash函式個數,m是bit陣列的長度,n是加入值的個數),出錯概率是最小的。
當然,如果我們要移除值,怎麼辦呢?當前的結構是沒法實現的,我們可以通過在加一個等長的資料,存放每個bit位設定為1的次數,設定一次加1,取消一次減一。
package com.ljt.algorithm;
import java.util.BitSet;
/**
* BloomFilter演算法,是一種大資料排重演算法。在一個數據量很大的集合裡,能準確斷定一個物件不在集合裡;判斷一個物件有可能在集合裡,而且佔用的空間不大。它不適合那種要求準確率很高的情況,零錯誤的場景。通過犧牲部分準確率達到高效利用空間的目的。
*
* 場景一:假如有一個很大的表,通過欄位key查詢資料,操作很重;業務方請求時,傳過來的key有很大一部分是不存在的;這種不存在的key請求就會浪費我們的查詢資源。針對這種情況,我們可以引人BloomFilter演算法,在請求key查詢之前,使用BloomFilter匹配。如果不存在,就不用去查詢了(正確率百分之百);如果存在,走原來的查詢流程(有可能不存在的key混進去了)。
*
* 場景二:假如有一個很大的表,通過欄位key判斷是否存在,操作很重,如果存在就做一些操作,不存在就加入表中;可容許一定的誤判。對應這種情況,我們也可以引入BloomFilter演算法,通過key查詢表判斷存在否的方式可換成BloomFilter演算法。如果存在,我們執行以前的邏輯(有一定的誤判,業務也允許一定的錯誤);如果不存在,也執行以前的邏輯。
*
* BloomFilter是由一個長度為n的bit陣列S和k個hash演算法組成。先使bit陣列的初始值為0.
* 新增值M:M經過k個hash演算法計算後,得到:M1, M2 … Mk; 然後,使S[M1]=1,S[M2]=2... S[Mk]=1
* 判斷值Y:Y經過k個hash演算法計算後,得到:Y1,Y2... Yk。 然後,判斷S[Y1],S[Y2] … S[Yk]
* 是否都為1。如果有一個不為1,那這個Y就一定是不存在的,以前沒新增過;如果都為1,那這個Y可能存在,也可能其他值新增後,影響了這次判斷的結果。
*
* 我們要做的是儘量降低正確判斷的誤判率,資料顯示, 當 k = ln(2)* m/n
* 時(k是hash函式個數,m是bit陣列的長度,n是加入值的個數),出錯概率是最小的。
*
* 當然,如果我們要移除值,怎麼辦呢?當前的結構是沒法實現的,我們可以通過在加一個等長的資料,存放每個bit位設定為1的次數,設定一次加1,取消一次減一。
*/
public class SimpleBloomFilter {
public static final int BLOOMSIZE = 2 << 24; // 規定bloom的長度24bits
public static final int[] seeds = { 3, 5, 7, 11, 13, 31, 37, 61, 131 }; // 8個hashset函式
private BitSet bits = new BitSet(BLOOMSIZE); // 定義一個24位長的bit 所有位初始值都是false
// 把字串加到布隆濾波器中,簡而言之就是把該字串的相應hashcode對映到bits上
public boolean add(String s) {
if (s.equals("") || (s == null)) {
return false;
}
for (int i = 0; i < seeds.length; i++) {
HashCodeGen hcg = new HashCodeGen(seeds[i]);
int codeGen = hcg.hashCodeGen(s);
bits.set(codeGen, true);
}
return true;
}
// 判斷該字串是否存在
public boolean contain(String s) throws Exception {
if (s.equals("") || (s == null)) { // 輸入的字串需要控制
throw new Exception("非法輸入字串");
}
boolean ret = true;
for (int i = 0; i < seeds.length; i++) {
HashCodeGen hcg = new HashCodeGen(seeds[i]);// 生成seeds長度的物件
ret = ret && bits.get(hcg.hashCodeGen(s));
if (ret == false)
break;
}
return ret;
}
public static void main(String[] args) {
SimpleBloomFilter bfn = new SimpleBloomFilter();
bfn.add("www.baidu.com");
try {
System.out.println(bfn.contain("www.baidu.com.cn"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
class HashCodeGen {
private int seed = 0;
public HashCodeGen(int seed) {
this.seed = seed;
}
// 生成hash碼
public int hashCodeGen(String s) {
int hash = 0;
for (int i = 0; i < s.length(); i++) {
hash = hash * seed + s.charAt(i);
}
return (hash & 0x7ffffff);
}
}