1. 程式人生 > >大資料排重演算法-布隆演算法(BloomFilter)

大資料排重演算法-布隆演算法(BloomFilter)

前續:網頁上已經有很多布隆過濾器很全的資料了,由於博主最近在做網頁爬蟲,遇到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); } }