布隆过滤器(Bloom Filter)及 JavaAPI

1. Bloom Filter算法简介

Bloom-Filter,即布隆过滤器,1970年由Bloom中提出。它能够用于检索一个元素是否在一个集合中。java

Bloom Filter(BF)是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。它是一个判断元素是否存在集合的快速的几率算法。Bloom Filter有可能会出现错误判断,但不会漏掉判断。也就是Bloom Filter判断元素再也不集合,那确定不在。若是判断元素存在集合中,有必定的几率判断错误。所以,Bloom Filter”不适合那些“零错误的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter比其余常见的算法(如hash,折半查找)极大节省了空间。 算法

它的优势是空间效率和查询时间都远远超过通常的算法,缺点是有必定的误识别率和删除困难。数组

2. Bloom Filter基本思想

 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

https://images2015.cnblogs.com/blog/567993/201603/567993-20160317225223334-1930594904.jpg

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“处)。   

https://images2015.cnblogs.com/blog/567993/201603/567993-20160317225223693-1768052613.jpg

 3)判断元素是否存在集合

    在判断y是否属于这个集合时,咱们只须要对y使用k个哈希函数获得k个哈希值,若是全部hashi(y)的位置都是1(1≤i≤k),即k个位置都被设置为1了,那么咱们就认为y是集合中的元素,不然就认为y不是集合中的元素。下图中y1就不是集合中的元素(由于y1有一处指向了“0”位)。y2或者属于这个集合,或者恰好是一个false positive。

https://images2015.cnblogs.com/blog/567993/201603/567993-20160317225223974-1196082220.jpg

一个Bloom Filter有如下参数:

m

bit数组的宽度(bit数)

n

加入其中的key的数量

k

使用的hash函数的个数

f

False Positive的比率

 

Bloom Filter的f知足下列公式:

https://images2015.cnblogs.com/blog/567993/201603/567993-20160317225224396-508919770.png

 

在给定mn时,可以使f最小化的k值为:

https://images2015.cnblogs.com/blog/567993/201603/567993-20160317225224756-1964138351.png

此时给出的f为:

https://images2015.cnblogs.com/blog/567993/201603/567993-20160317225225271-1692743268.png

根据以上公式,对于任意给定的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的关键公式。

4. BloomFilter的应用

    1.黑名单

       好比邮件黑名单过滤器,判断邮件地址是否在黑名单中

    2.排序(仅限于BitSet)

       仔细想一想,其实BitSet在set(int value)的时候,“顺便”把value也给排序了。

    3.网络爬虫

        判断某个URL是否已经被爬取过

    4.K-V系统快速判断某个key是否存在

       典型的例子有Hbase,Hbase的每一个Region中都包含一个BloomFilter,用于在查询时快速判断某个key在该region中是否存    在,若是不存在,直接返回,节省掉后续的查询。

4. JavaAPI

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)));
//			}
//		});
    }

}