布隆过滤器 (Bloom Filter)是由Burton Howard Bloom于1970年提出,它是一种space efficient的几率型数据结构,用于判断一个元素是否在集合中。在垃圾邮件过滤的黑白名单方法、爬虫(Crawler)的网址判重模块中等等常常被用到。哈希表也能用于判断元素是否在集合中,可是布隆过滤器只须要哈希表的1/8或1/4的空间复杂度就能完成一样的问题。布隆过滤器能够插入元素,但不能够删除已有元素。其中的元素越多,false positive rate(误报率)越大,可是false negative (漏报)是不可能的。java
一个empty bloom filter是一个有m bits的bit array,每个bit位都初始化为0。而且定义有k个不一样的hash function,每一个都以uniform random distribution将元素hash到m个不一样位置中的一个。在下面的介绍中n为元素数,m为布隆过滤器或哈希表的slot数,k为布隆过滤器重hash function数。web
为了add一个元素,用k个hash function将它hash获得bloom filter中k个bit位,将这k个bit位置1。算法
为了query一个元素,即判断它是否在集合中,用k个hash function将它hash获得k个bit位。若这k bits全为1,则此元素在集合中;若其中任一位不为1,则此元素比不在集合中(由于若是在,则在add时已经把对应的k个bits位置为1)。数据结构
不容许remove元素,由于那样的话会把相应的k个bits位置为0,而其中颇有可能有其余元素对应的位。所以remove会引入false negative,这是绝对不被容许的。dom
当k很大时,设计k个独立的hash function是不现实而且困难的。对于一个输出范围很大的hash function(例如MD5产生的128 bits数),若是不一样bit位的相关性很小,则可把此输出分割为k份。或者可将k个不一样的初始值(例如0,1,2, … ,k-1)结合元素,feed给一个hash function从而产生k个不一样的数。ide
当add的元素过多时,即n/m过大时(n是元素数,m是bloom filter的bits数),会致使false positive太高,此时就须要从新组建filter,但这种状况相对少见。svg
public class SimpleBloomFilter { private static final int DEFAULT_SIZE = 2 << 24 ; private static final int [] seeds = new int [] { 7 , 11 , 13 , 31 , 37 , 61 , }; private BitSet bits = new BitSet(DEFAULT_SIZE); private SimpleHash[] func = new SimpleHash[seeds.length]; public static void main(String[] args) { String value = " stone2083@yahoo.cn " ; SimpleBloomFilter filter = new SimpleBloomFilter(); System.out.println(filter.contains(value)); filter.add(value); System.out.println(filter.contains(value)); } public SimpleBloomFilter() { for ( int i = 0 ; i < seeds.length; i ++ ) { func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]); } } public void add(String value) { for (SimpleHash f : func) { bits.set(f.hash(value), true ); } } public boolean contains(String value) { if (value == null ) { return false ; } boolean ret = true ; for (SimpleHash f : func) { ret = ret && bits.get(f.hash(value)); } return ret; } public static class SimpleHash { private int cap; private int seed; public SimpleHash( int cap, int seed) { this .cap = cap; this .seed = seed; } public int hash(String value) { int result = 0 ; int len = value.length(); for ( int i = 0 ; i < len; i ++ ) { result = seed * result + value.charAt(i); } return (cap - 1 ) & result; } } }
import java.nio.charset.Charset; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnel; import com.google.common.hash.PrimitiveSink; public class BloomTest { // 预计元素个数 private long size = 1024 * 1024; private BloomFilter<String> bloom = BloomFilter.create(new Funnel<String>() { @Override public void funnel(String from, PrimitiveSink into) { // 自定义过滤条件 此处不作任何过滤 into.putString((CharSequence) from, Charset.forName("UTF-8")); } }, size); public void fun() { // 往过滤器中添加元素 bloom.put("布隆过滤器"); // 查询 boolean mightContain = bloom.mightContain("布隆过滤器"); System.out.println(mightContain); } public static void main(String[] args) { BloomTest blBoolmTest = new BloomTest(); blBoolmTest.fun(); } }