前续:网页上已经有很多布隆过滤器很全的资料了,由于博主最近在做网页爬虫,遇到url防重问题,所以认真分析了布隆滤波器原理,也参考了相关博文。旨在给出不同人对其不同的理解,好给大家更全面的参考。
1、布隆过滤器原理
布隆过滤器=位图+哈希。一个空的布隆过滤器是一个m位的位图,所以位值开始均为0,定义k个不同的符合均匀随机分布的哈希函数,每个函数把集合元素映射到位图m位中的某一位。插入时,先把这个元素作为k个哈希函数的输入,拿到k组数组位置,然后把这所有的位置设置为1,。查询时,把这个元素作为k个哈希函数的输入,得到k组数组位置。这些位置只要有任意一个是0,元素肯定不会在这个集合里面。如果元素在集合里面,那么这些位置在元素插入时都被设置为1了。如果这些位置都是1,那么要么元素在集合里面,要么所有这些位置在其他元素插入时都被设置为1了,发生了“虚报”。
该过程这样理解:首先插入 “A” “B” “C”三个元素,可以通过散列表拿到A B C所对应的哈希码。如果 A元素对应的哈希码为 000101,位图开始是m位的全零的bits,那我把位图的最低位和从最低位开始第三位设置为1。B也是同样的道理,如果B对应的哈希码为0010,那么现在的位图为000111,如果c对应为10000,此时位图为010111,如果再来一个c,发现位图的最低位开始第五位为1,那么证明该位图中含有c元素,所以不对c元素进行存储。这里存在另一种情况,如果a对应的是001111,b对应的是000001,先进来a,然后进来b的时候发现最低位置一了,就会判断b已经存在(其实b还没存在集合中),所以就会产生漏判。但是不会出现误判现象,即a已经存在集合中,但是判断的时候说没存在集合中,这种情况不会产生。所以该布隆过滤器会存在一个漏判率。
优缺点:
优点:1、存储空间和插入/查询时间都是常数,远远超过一般的算法;
2、Hash函数相互之间没有关系,生成hash码时可以并行处理;
3、不需要存储本身值,对保密性要求高的场合有很强的实用性;
4、耗费空间减少到散列表的1/8到1/4;
缺点:1、有一定的漏判概率;
2、删除元素有困难,这个也好理解A对应的码是001111,B如果对应的码是000001,那么删除A时无法保证不误删掉和A共用最低位B对应的码。所以删除很不方便,如果要保证正确删除的话,需要做辅助变量和相应逻辑。
布隆滤波器的误报率介绍: 给定一个布隆过滤器,m是该位数组的大小,K是hash函数的个数,n是要插入的元素个数,则误报的概率近似为:,可以看出:随着位数m的增加,假正例(false positives)的概率会下降,同时随着插入元素的个数n的增加,假正例的概率会增加,所以误报率最小的k约等于0.7*m/n。
2、java代码实现
package edu.bit.ys.spider.method;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
public class BloomFilterNew {
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) {
BloomFilterNew bfn=new BloomFilterNew();
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);
}
}
搞明白其原理,代码就相对好编写了。这里就hashCodeGen说明一下其来源:该哈希码的生成用的是BKDRhash,BKDRhash的c/c++代码如下:
unsigned int BKDRhash(char *s)
{
unsigned int seed=131;//31 131 13131 131313 etc...
unsigned int hash=0;
while(*s)
{
hash=hash*seed+(*str++);
}
return (hash & 0x7FFFFFFF);
}
该哈希码生成算法用的相对很广泛,然后返回的是hash&(2^24-1)。这就是为什么在很多程序里面代码:c=2<<24; return hash & (c-1);了。这些代码是有其具体的原型。