Bloom-Filter,即布隆过滤器,1970年由Bloom中提出。它能够用于检索一个元素是否在一个集合中。java
Bloom Filter(BF)是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。它是一个判断元素是否存在集合的快速的几率算法。Bloom Filter有可能会出现错误判断,但不会漏掉判断。也就是Bloom Filter判断元素再也不集合,那确定不在。若是判断元素存在集合中,有必定的几率判断错误。所以,Bloom Filter”不适合那些“零错误的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter比其余常见的算法(如hash,折半查找)极大节省了空间。 算法
它的优势是空间效率和查询时间都远远超过通常的算法,缺点是有必定的误识别率和删除困难。数组
Bloom-Filter算法的核心思想就是利用多个不一样的Hash函数来解决“冲突”。网络
计算某元素x是否在一个集合中,首先能想到的方法就是将全部的已知元素保存起来构成一个集合R,而后用元素x跟这些R中的元素一一比较来判断是否存在于集合R中;咱们能够采用链表等数据结构来实现。可是,随着集合R中元素的增长,其占用的内存将愈来愈大。试想,若是有几千万个不一样网页须要下载,所需的内存将足以占用掉整个进程的内存地址空间。即便用MD5,UUID这些方法将URL转成固定的短小的字符串,内存占用也是至关巨大的。数据结构
因而,咱们会想到用Hash table的数据结构,运用一个足够好的Hash函数将一个URL映射到二进制位数组(位图数组)中的某一位。若是该位已经被置为1,那么表示该URL已经存在。函数
Hash存在一个冲突(碰撞)的问题,用同一个Hash获得的两个URL的值有可能相同。为了减小冲突,咱们能够多引入几个Hash,若是经过其中的一个Hash值咱们得出某元素不在集合中,那么该元素确定不在集合中。只有在全部的Hash函数告诉咱们该元素在集合中时,才能肯定该元素存在于集合中。这即是Bloom-Filter的基本思想。this
1)位数组:url
假设Bloom Filter使用一个m比特的数组来保存信息,初始状态时,Bloom Filter是一个包含m位的位数组,每一位都置为0,即BF整个数组的元素都设置为0。spa
2)添加元素,k个独立hash函数3d
为了表达S={x1, x2,…,xn}这样一个n个元素的集合,Bloom Filter使用k个相互独立的哈希函数(Hash Function),它们分别将集合中的每一个元素映射到{1,…,m}的范围中。
当咱们往Bloom Filter中增长任意一个元素x时候,咱们使用k个哈希函数获得k个哈希值,而后将数组中对应的比特位设置为1。即第i个哈希函数映射的位置hashi(x)就会被置为1(1≤i≤k)。
注意,若是一个位置屡次被置为1,那么只有第一次会起做用,后面几回将没有任何效果。在下图中,k=3,且有两个哈希函数选中同一个位置(从左边数第五位,即第二个“1“处)。
3)判断元素是否存在集合
在判断y是否属于这个集合时,咱们只须要对y使用k个哈希函数获得k个哈希值,若是全部hashi(y)的位置都是1(1≤i≤k),即k个位置都被设置为1了,那么咱们就认为y是集合中的元素,不然就认为y不是集合中的元素。下图中y1就不是集合中的元素(由于y1有一处指向了“0”位)。y2或者属于这个集合,或者恰好是一个false positive。
m |
bit数组的宽度(bit数) |
n |
加入其中的key的数量 |
k |
使用的hash函数的个数 |
f |
False Positive的比率 |
Bloom Filter的f知足下列公式:
在给定m和n时,可以使f最小化的k值为:
此时给出的f为:
根据以上公式,对于任意给定的f,咱们有:
n = m ln(0.6185) / ln(f)
同时,咱们须要k个hash来达成这个目标:
k = - ln(f) / ln(2)
因为k必须取整数,咱们在Bloom Filter的程序实现中,还应该使用上面的公式来求得实际的f:
f = (1 – e-kn/m)k
以上3个公式是程序实现Bloom Filter的关键公式。
1.黑名单
好比邮件黑名单过滤器,判断邮件地址是否在黑名单中
2.排序(仅限于BitSet)
仔细想一想,其实BitSet在set(int value)的时候,“顺便”把value也给排序了。
3.网络爬虫
判断某个URL是否已经被爬取过
4.K-V系统快速判断某个key是否存在
典型的例子有Hbase,Hbase的每一个Region中都包含一个BloomFilter,用于在查询时快速判断某个key在该region中是否存 在,若是不存在,直接返回,节省掉后续的查询。
public class BloomFilter { private static final int BIT_SIZE = 2 << 28 ;//二进制向量的位数,至关于能存储1000万条url左右,误报率为千万分之一 private static final int[] seeds = new int[]{3, 5, 7, 11, 13, 31, 37, 61};//用于生成信息指纹的8个随机数,最好选取质数 private BitSet bits = new BitSet(BIT_SIZE); private Hash[] func = new Hash[seeds.length];//用于存储8个随机哈希值对象 public BloomFilter2(){ for(int i = 0; i < seeds.length; i++){ func[i] = new Hash(BIT_SIZE, seeds[i]); } } /** * 像过滤器中添加字符串 */ public void addValue(String value) { //将字符串value哈希为8个或多个整数,而后在这些整数的bit上变为1 if(value != null){ for(Hash f : func) bits.set(f.hash(value), true); } } /** * 判断字符串是否包含在布隆过滤器中 */ public boolean contains(String value) { if(value == null) return false; boolean ret = true; //将要比较的字符串从新以上述方法计算hash值,再与布隆过滤器比对 for(Hash f : func) ret = ret && bits.get(f.hash(value)); return ret; } /** * 随机哈希值对象 */ public static class Hash{ private int size;//二进制向量数组大小 private int seed;//随机数种子 public Hash(int cap, int seed){ this.size = 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 (size - 1) & result; } } public static void main(String[] args) { String[] values = { "www.baidu.com", "www.iqiyi.com", "weibo.com", "mail.163.com", "www.baidu.com"" }; BloomFilter2 bf = new BloomFilter2(); for (int i = 0; i < values.length; i++) { System.out.println("\nURL: " + values[i]); System.out.println("加入前"); System.out.println("是否已经存在:" + bf.contains(values[i])); bf.addValue(values[i]); System.out.println("加入后"); System.out.println("是否已经存在:" + bf.contains(values[i])); } // Arrays.stream(values).forEach(str -> { // for(int i = 0; i < str.length(); i++){ // System.out.println(str+": hashcode"+i+":"+(int)(str.charAt(i))); // } // }); } }