布隆过滤器——Bloom Filter

 

谷歌的数学之美系列曾经提到过一种数据结构叫作bloomfilter,翻译成中文就是布隆过滤,文中使用布隆过滤器来过滤黑名单。后来我在毕业设计中也用到了它来过滤重复的URL,避免网络爬虫重复抓取。再后来在单位又一次的用到了bloomfilter来过滤用户的重复访问。随着海量数据时代的到来,布隆过滤器应用的场景愈来愈多。java

布隆过滤器(Bloom Filter)是1970年由Burton Howard Bloom提出的。它其实是一个很长的二进制向量和一系列随机映射函数。布隆过滤器能够用于检索一个元素是否在一个集合中。它的优势是空间效率和查询时间都远远超过通常的算法,缺点是有必定的误识别率和删除困难。算法

初始状态时,Bloom Filter是一个包含m位的位数组,每一位都置为0数组


 

为了表达S={X1, X2,,Xn}这样一个n个元素的集合,Bloom Filter使用k个相互独立的哈希函数(Hash Function),它们分别将集合中的每一个元素映射到{1,,m}的范围中。对任意一个元素X,第i个哈希函数映射的位置Hi(X)就会被置为11ik)。注意,若是一个位置屡次被置为1,那么只有第一次会起做用,后面几回将没有任何效果。在下图中,k=3,且有两个哈希函数选中同一个位置(从左边数第五位)。   网络

 


 


在判断Y是否属于这个集合时,咱们对Y应用k次哈希函数,若是全部Hi(Y)的位置都是11ik),那么咱们就认为Y是集合中的元素,不然就认为Y不是集合中的元素。下图中Y1就不是集合中的元素。Y2或者属于这个集合,或者恰好是一个误判。数据结构


应用布隆过滤器时,只须要由用户决定要容纳的元素数n和但愿的误判率p。而后经过如下公式:dom


计算出位数组的长度m,而后经过mn计算出哈希函数的个数k函数


 

隆过滤器的优劣主要与哈希函数的质量相关,并且哈希函数之间的相关度越小越好,每一个哈希函数自己的计算过程不要太复杂,否则会影响效率。理想状况下是取k个彻底不相关的哈希函数,在不是很严格状况下,也能够经过一个哈希函数的参数变化产生k个不一样的哈希函数,好比将i1ik)做为参数参与哈希函数的计算。this

不一样的应用场景,哈希函数的设计方法不一样,没有通用的规律可循。在网络爬虫的设计中,我才用了MD5算法最为基础来构造哈希函数:url

for (int i = 0; i < funNum; i++){ //输入URL地址拼接上Hash函数的编号 String input = url+i.toString(); //散列值取MD5摘要的后64位与比特向量大小的的余数 hash =(long)Md5(input).getLast64bit() % (long)bitSetSize; }spa

 

在过滤用户时,因为用户ID是一个long型数据,所以用随机数函数random()效率更高。下面是long类型bloomfilter的完整实现。

 

import java.io.Serializable; import java.util.BitSet; import java.util.Random; /** * Long类型元素的布隆过滤器 */ public class BloomFilter implements Serializable { private static final long serialVersionUID = 1L; public static final int ELEM_NUM = 1000; // 欲容纳的元素个数 public static final double PERCENTAGE = 0.001; // 但愿的偏差率 private int hashNum; // hash函数的数量 private int size; // 位向量的長度 private BitSet bitVecter; // 位向量 public BloomFilter() { size = (int) Math.abs(ELEM_NUM * Math.log(PERCENTAGE) / (Math.log(2) * Math.log(2))) + 1; hashNum = (int) (Math.log(2) * ((double) size / ELEM_NUM)); bitVecter = new BitSet(size); } /** * 查找元素是否在集合中 */ public boolean search(Long elem) { boolean flag = true; int temp; Random random = new Random(elem); for (int i = 0; i < hashNum; i++) { temp = random.nextInt(size); if (!bitVecter.get(temp)) {// 元素不在集合中 bitVecter.set(temp); flag = false; } } return flag; } /** * 获取位向量的长度 */ public int size() { return bitVecter.size(); }

 

public int getHashNum() { return hashNum; } public void setHashNum(int hashNum) { this.hashNum = hashNum; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public BitSet getBitVecter() { return bitVecter; } public void setBitVecter(BitSet bitVecter) { this.bitVecter = bitVecter; } }