Bloom Filter 基本原理以及Java实现
Bloom Filter用于检测元素是否存在于某一集合内,其特点在于:结果判断不在该集合的元素一定不在该集合中,判断在该集合内的元素可能并不在该集合内
应用场景:
大规模的,这里可以认为是上千万甚至亿条数据集的查重,且对误报不敏感,常用于判断某一条url是否已经被处理过或者是否存在这样一条url在数据集中。
基本原理:
1、计算位数组
对一个集合中的元素通过长度为k的散列函数组进行散列,得到k个索引位置,将事先建立的位数组中这k个位置设为1,重复上述过程,直到遍历完集合中所有的元素。
2、判断某一元素是否存在于该集合中
对该元素用同样的散列组进行散列,得到k个索引,检查位数组中的k个索引位置的值是否都为1,若都为1,则该元素可能存在于该集合中,若存在0,则该元素一定不存在于该集合中
关于False Positive 的概率计算,位数组长度和散列函数数量的选择问题:
相关的数学证明可以参考:
设位数组长度为m,误报概率为p,集合总大小为n,散列函数数量为k,则有以下关系:
Bloom Filter一种Java实现:
package com.ryo.algorithm; /** * <strong>布隆过滤器</strong><br> * <p>预设的误报概率为0.01。0.01与0.001有不小的性能差距,而且调整为0.001后散列函数数量也要相应的上调才能保持最优<br> * 实测中向构造器传入1000万长度的数组,建立该布隆过滤器平均约需要4.0s的时间,建立的位数组长度约为9500万<br> * 位数组长度与集合数组长度的比例受期望误报概率p的影响<br> * 性能上主要还可以从散列函数的算法上进行优化,大量的散列是主要的耗时操作<br> * 在数据量较小的时候布隆过滤器的查找性能不如一般的哈希表。该类没有对当集合元素数量非常小的情况作处理 * </p> * @param <T> 过滤器元素类型 * @author shiin */ public class BloomFilter<T> { /** * 期望的误报概率<br> */ private static final float p = 0.01f; /** * 散列函数数量<br> * 该参数实际上需要根据其他参数进行调整<br> * 但要写出很多个适用于T的效率较高不易冲突的散列函数实际上不是简单的事情<br> * 在这里只实现了4个简单的散列函数,并不保证这些函数是最优的<br> * 剩余两个索引位置的计算由4个散列函数中挑选两个进行一次再hash得到 */ private static final int k = 6; /** * 过滤器集合数组 */ private T[] n; /** * 散列结果集 由0,1构成 */ private int[] resultSet; /** * =resultSet.length; */ private int len; /** * constructor * @param arr 过滤器集合元素数组 */ public BloomFilter(T[] arr){ if(arr != null && arr.length > 0){ n = arr; len = (int)(-1.44*arr.length*Math.log(p)/Math.log(2)); resultSet = new int[len]; buildBloomFilter(); } } /** * 检查en是否存在于过滤器集合中 * @param en 待检查的元素 * @return true:可能在也可能不在集合中 / false:一定不在集合中 */ public boolean contains(T en){ int[] index = getIndexGroup(en); for(int i : index){ if(resultSet[i] == 0) return false; } return true; } /*——————————————————————————private part——————————————————————————————*/ /** * 构建BloomFilter */ private void buildBloomFilter(){ for(int i=0 ;i<n.length ;i++){ for(int index : getIndexGroup(n[i])){ if(resultSet[index] == 0) resultSet[index] = 1; } } } /** * 散列函数组<br> * 常量k = 6 * @return 返回T en多个散列后得到的索引 */ private int[] getIndexGroup(T en){ int[] index = new int[k]; int baseCode = baseHash(en); index[0] = hash1(baseCode); index[1] = hash2(baseCode); index[2] = hash3(baseCode); index[3] = hash4(baseCode); index[4] = hash2(index[1]); index[5] = hash2(index[2]); return index; } /*————————————————————————————散列函数集——————————————————————————该部分有较大的优化空间*/ private int baseHash(T en){ int baseCode; if((baseCode = en.hashCode()) < 0) baseCode = -baseCode; return baseCode; } /** * 直接取余 */ private int hash1(int baseCode){ return baseCode%len; } /** * 32bit Mix Functions */ private int hash2(int baseCode){ int h = baseCode+~(baseCode<<15); h ^= (h>>10); h += (h<<3); h ^= (h>>6); h += ~(h<<11); h ^= (h>>16); if(h < 0) h = -h; return h%len; } /** * 乘以自然常数e取其小数部分乘以len再取整数部分即得到索引<br> * 该hash算法计算效率较低 */ private int hash3(int baseCode){ double h = baseCode*Math.E; return (int)((h-(long)h)*len); } /** * 利用String提供的散列方法 */ private int hash4(int baseCode){ String str = baseCode+""+~baseCode; int h = str.hashCode(); if(h < 0) h = -h; return h%len; } }