前续:网页上已经有很多布隆过滤器很全的资料了,由于博主最近在做网页爬虫,遇到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);
}
}