大數據處理算法二:Bloom Filter算法

百度面試題:給定a、b兩個文件,各存放50億個url,每個url各佔64字節,內存限制是4G,讓你找出a、b文件共同的url?

Bloom Filter是由Bloom1970年提出的一種多哈希函數映射的快速查找算法。通常應用在一些需要快速判斷某個元素是否屬於集合,但是並不嚴格要求100%正確的場合。

實例 

  爲了說明Bloom Filter存在的重要意義,舉一個實例:

   (實例一),假設要你寫一個網絡蜘蛛(web crawler)。由於網絡間的鏈接錯綜複雜,蜘蛛在網絡間爬行很可能會形成。爲了避免形成,就需要知道蜘蛛已經訪問過那些URL。給一個URL,怎樣知道蜘蛛是否已經訪問過呢?稍微想想,

         (實例二)給定ab兩個文件,各存放50億個url,每個url各佔64字節,內存限制是4G,讓你找出ab文件共同的url

就會有如下幾種方案:

  1. 將訪問過的URL保存到數據庫。

  2. HashSet將訪問過的URL保存起來。那隻需接近O(1)的代價就可以查到一個URL是否被訪問過了。

  3. URL經過MD5SHA-1等單向哈希後再保存到HashSet或數據庫。

  4. Bit-Map方法。建立一個BitSet,將每個URL經過一個哈希函數映射到某一位。

  方法1~3都是將訪問過的URL完整保存,方法4則只標記URL的一個映射位。

       以上方法在數據量較小的情況下都能完美解決問題,但是當數據量變得非常龐大時問題就來了。

  方法1的缺點:數據量變得非常龐大後關係型數據庫查詢的效率會變得很低。而且每來一個URL就啓動一次數據庫查詢是不是太小題大做了?

  方法2的缺點:太消耗內存。隨着URL的增多,佔用的內存會越來越多。就算只有1億個URL,每個URL只算50個字符,就需要5GB內存。

  方法3:由於字符串經過MD5處理後的信息摘要長度只有128BitSHA-1處理後也只有160Bit,因此方法3比方法2節省了好幾倍的內存。

  方法4消耗內存是相對較少的,但缺點是單一哈希函數發生衝突的概率太高。還記得數據結構課上學過的Hash表衝突的各種解決方法麼?若要降低衝突發生的概率到1%,就要將BitSet的長度設置爲URL個數的100倍。

  實質上上面的算法都忽略了一個重要的隱含條件:允許小概率的出錯,不一定要100%準確!也就是說少量url實際上沒有沒網絡蜘蛛訪問,而將它們錯判爲已訪問的代價是很小的——大不了少抓幾個網頁唄。 

例如有 一組字符 arr:”哈哈“,”呵呵“........

字符串:“哈哈”

哈希算法1處理後:8

哈希算法2處理後:1

哈希算法1處理後:3

插入BitArray後


再處理字符串:“呵呵”

哈希算法1處理後:2

哈希算法2處理後:1

哈希算法1處理後:9

 

繼續插入BitArray後,如果繼續遊字符串,繼續以這種方式插入

 

判斷”在這些字符串是否包含”嘻嘻“

哈希算法1處理後:0

哈希算法2處理後:1

哈希算法1處理後:7

只要判斷 下標分別爲 0,1,7位置的值是否都爲1,如下圖 因爲位置0跟位置7的值不爲1

所以”嘻嘻“不包含在arr中,反之如果都爲1怎包含


java代碼實現如下

import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;

/**
 * BloomFilter算法
 * 
 * @author JYC506
 * 
 */
public class BloomFilter {
    /*哈希函數*/
	private List<IHashFunction> hashFuctionList;
    /*構造方法*/
	public BloomFilter() {
		this.hashFuctionList = new ArrayList<IHashFunction>();
	}
    /*添加哈希函數類*/
	public void addHashFunction(IHashFunction hashFunction) {
		this.hashFuctionList.add(hashFunction);
	}
    /*刪除hash函數*/
	public void removeHashFunction(IHashFunction hashFunction) {
		this.hashFuctionList.remove(hashFunction);
	}
    /*判斷是否被包含*/
	public boolean contain(BitSet bitSet, String str) {
		for (IHashFunction hash : hashFuctionList) {
			int hashCode = hash.toHashCode(str);
			if(hashCode<0){
				hashCode=-hashCode;
			}
			if (bitSet.get(hashCode) == false) {
				return false;
			}
		}
		return true;
	}
    /*添加到bitSet*/
	public void toBitSet(BitSet bitSet, String str) {
		for (IHashFunction hash : hashFuctionList) {
			int hashCode = hash.toHashCode(str);
			if(hashCode<0){
				hashCode=-hashCode;
			}
			bitSet.set(hashCode, true);
		}
	}
    
	public static void main(String[] args) {
		BloomFilter bloomFilter=new BloomFilter();
		/*添加3個哈希函數*/
		bloomFilter.addHashFunction(new JavaHash());
		bloomFilter.addHashFunction(new RSHash());
		bloomFilter.addHashFunction(new SDBMHash());
		/*長度爲2的24次方*/
		BitSet bitSet=new BitSet(1<<25);
		/*判斷test1很test2重複的字符串*/
		String[] test1=new String[]{"哈哈","我","大家","逗比","有錢人性","小米","Iphone","helloWorld"};
		for (String str1 : test1) {
			bloomFilter.toBitSet(bitSet, str1);
		}
		String[] test2=new String[]{"哈哈","我的","大家","逗比","有錢的人性","小米","Iphone6s","helloWorld"};
		for (String str2 : test2) {
			if(bloomFilter.contain(bitSet, str2)){
				System.out.println("'"+str2+"'是重複的");
			}
		}
		
	}
}
/*哈希函數接口*/
interface IHashFunction {
	int toHashCode(String str);
}

class JavaHash implements IHashFunction {

	@Override
	public int toHashCode(String str) {
		return str.hashCode();
	}

}

class RSHash implements IHashFunction {

	@Override
	public int toHashCode(String str) {
		int b = 378551;
		int a = 63689;
		int hash = 0;
		for (int i = 0; i < str.length(); i++) {
			hash = hash * a + str.charAt(i);
			a = a * b;
		}
		return hash;
	}

}

class SDBMHash implements IHashFunction {

	@Override
	public int toHashCode(String str) {
		int hash = 0;
		for (int i = 0; i < str.length(); i++)
			hash = str.charAt(i) + (hash << 6) + (hash << 16) - hash;
		return hash;
	}

}



運行結果


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章