布隆过滤器
如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢。
布隆过滤器是一种空间效率很高的随机数据结构,Bloom filter 可以看做是对 bit-map 的扩展,它的实现原理如下:
当一个元素被加入集合时,通过k个hash函数将这个元素映射为一个位阵列中的k个点,并把他们的值设置为1。检索的时候我们同样以k个hash函数得到k个值,然后判断在位阵列中对应的k个位置的值是否全为1.如果全为1,被检索元素很可能存在,否则被检索元素一定不存在。
布隆过滤器的优点:
1.空间效率和查询效率都很高,布隆过滤器的插入效率和查询效率都为O(k),并且散列函数之间没有任何的联系
2.布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势
布隆过滤器的的缺点:
1.随着存入的元素数量增加,误算率
随之增加,误判率表格如下:
2.一般情况下不能从布隆过滤器中删除
元素. 我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加 1, 这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。
布隆过滤器的扩展
基本的布隆过滤器不支持删除(Deletion)操作,但是 Counting filters 提供了一种可以不用重新构建布隆过滤器但却支持元素删除操作的方法。在Counting filters中原来的位数组中的每一位由 bit 扩展为 n-bit 计数器,实际上,基本的布隆过滤器可以看作是只有一位的计数器的Counting filters。原来的插入操作也被扩展为把 n-bit 的位计数器加1,查找操作即检查位数组非零即可,而删除操作定义为把位数组的相应位减1,但是该方法也有位的算术溢出问题,即某一位在多次删除操作后可能变成负值,所以位数组大小 m 需要充分大。另外一个问题是Counting filters不具备伸缩性,由于Counting filters不能扩展,所以需要保存的最大的元素个数需要提前知道。否则一旦插入的元素个数超过了位数组的容量,false positive的发生概率将会急剧增加。当然也有人提出了一种基于 D-left Hash 方法实现支持删除操作的布隆过滤器,同时空间效率也比Counting filters高。
布隆过滤器的实现:
/** * 布隆过滤器的实现 */ public class BloomFilter { //二进制向量位数 private static final int BlOOM_BIT_SIZE = 2 << 28; //用于生成信息文的八个随机数 private static int[] seeds = new int[]{3,5,7,11,13,17,19,23}; private BitSet bits = new BitSet(BlOOM_BIT_SIZE); private Hash[] hashs = new Hash[seeds.length]; public BloomFilter() { for(int i=0;i<seeds.length;i++){ hashs[i] = new Hash(BlOOM_BIT_SIZE,seeds[i]); } } /** * 向布隆过滤器添加值 * @param value */ public void addValue(String value){ if(value != null && !"".equals(value)){ for(Hash hash : hashs){ bits.set(hash.hash(value),true); } } } /** * 判断value是否在布隆过滤器中 * @param value * @return */ public boolean contains(String value){ boolean result = true; if (value == null || "".equals(value)) { result = false; }else{ for(Hash hash:hashs){ result = result && bits.get(hash.hash(value)); } } return result; } /** * 随机hash值对象 */ static class Hash{ private int size;//二进制向量大小 private int seed;//随机种子 public Hash(int size, int seed) { this.size = size; this.seed = seed; } public int hash(String value){ int result = 0; for(int i=0;i<value.length();i++){ result = seed * result + value.charAt(i); } return result; } } }