布隆过滤器

Oct 25, 2016


题记

由于前段时间面试,被面试官问到布隆过滤器的工作原理(是怎么hash处理?),当时只是了解过布隆过滤器并没有答出来

所以今天正好有时间补上这一问题

这也提示我不只能光了解这个东西是什么?

更多的是要去明白为什么需要这个东西? 是如何工作的?

布隆过滤器

什么是位图?

package cn.march.model.bloom_filter;

/**
 * 
 *
 * 问题描述:
 *
 *      利用位图(bitMap)对数组进行排序:
 *      位图:
 *          简单来讲就是数组存放数据,若存在这个数据则将值置为1或者true
 *          比如现在有数组int[]{4,3,2,6} 利用位图来进行存储的话:
 *          arrays[2] = 1, arrays[3] = 1, arrays[4] = 1, arrays[6] = 1
 *          这样可以通过数组index表示值,1代表其值存在
 *
 *
 */
public class BitMap_Sort {
	
	
	//测试方法
	public static void main(String[] args) {

    		int[] arrays = new int[]{4,3,2,9,8,10,12};

    		bitmap_sort(arrays);

    		for(int array : arrays)
        		System.out.print(array + " ");

		}

	/**
	 * 基本思路:
	 *
	 *      首先计算数组的最值:maxmin
	 *      通过max-min+1计算得到位图数组的实际容量new int[max-min+1]
	 *      通过arrays的值对bit_arrays进行初始化;
	 *      考虑到arrays的值存在负数,比如:{1,4,-3,2}
	 *      最小值是-3,所以在对bit_arrays计算的时候:
	 *             for(int array : arrays)
	 *                  bit_arrays[array-min]++;
	 *      这样就消除了负数的影响i
	 *      之后就根据位图来排序arrays
	 *
	 * @param arrays
	*/
	public static void bitmap_sort(int[] arrays) {

		int max = arrays[0];
		int min = max;
		
		//arrays的最值
		for(int array : arrays) {

			if(max < array)
				max = array;
			if(min > array)
				min = array;

		}

		//bit_arrays位图数组进行初始化
		int[] bit_arrays = new int[max-min+1];

		for(int array : arrays)
			bit_arrays[array-min]++;

		int index = 0;

		//arrays进行排序;
		for(int i = 0; i < bit_arrays.length; i++) {

			while(bit_arrays[i] > 0) {

				arrays[index] = i + min;
				index++;
				bit_arrays[i]--;

			}

		}

	}

}

从上面代码我们可以清晰的了解,位图实际就是一个特殊的数组

位图的优势:

  • 1.能够快速的判断数据是否存在
  • 2.排序

位图的劣势:

  • 若数据之间的差距太大(最大值,最小值),耗费空间

比如arrays[1] = 1, arrays[4] = 0, arrays[..] = 0, arrays[9999] = 1;

会造成空间的严重浪费

如java.util.BitSet(实际上就是位图加上位操作)

所以为了解决这个问题,引入hash;

什么是布隆过滤器?

布隆过滤器能够快速的判断数据是否存在集合中

布隆过滤器结合了位图与hash的优势,所以其在空间与时间方面都有巨大的优势

但缺点就是误算率,随着存入的元素数量的增加,误算率随之增加

举个例子:

  • 对于数据k,v
  • hash(k) = 21,但是在某个情况下hash(v) = 21
  • 所以造成hash冲突,这样就导致k,v的误判

这是无可避免的,我们只能想办法降低误判率

我们举个实际例子来说明布隆过滤器是怎么工作的?

假设现在我们需要存储一亿个字符串比如是电子邮件:

dymdmy2120@gmail.com

1亿个邮件地址我们怎么判断我们输入的一个字符串是否存在?

  • 创建一个容量8亿的二进制数组bit_set;
  • 之后考虑对邮件地址进行hash计算,对于每一个邮件地址X,我们利用8个不同的hash种子(素数)产生8个不同的hash值,将bit_set对应hash值的位置置为1或者为true
  • 对这1亿个字符串处理存储好之后(由于数据量较大,我们可以采用分治,hash分割的方式进行处理)
  • 现在我们要查询邮件地址Y是否存在这个集合中,我们首先对Y获取其8个hash值,通过hash值判断bit_set对应的值是否为1或者true,若8个hash值对应的值都为1或者true则代表Y存在于集合中

代码如下:

package cn.march.model.bloom_filter;

	
import java.util.*;

public class BloomFilter {

    //布隆过滤器的比特长度
    private static final int DEFAULT_SIZE = 2<<24; 
	

    //选取质数,能很好的降低错误率
    private static final int[] seeds = {3,5,7,11,13,31,37,61};

    private static BitSet bits = new BitSet(DEFAULT_SIZE);
	
    //初始化一个哈希类数组
    private static SimpleHash[] func = new SimpleHash[seeds.length];

    /**
     * BitSet添加一个字符串
     * @param value
     */
    private static void addValue(String value) {

        for(SimpleHash fun : func) {

            bits.set(fun.hash(value), true);

        }

    }

    
    public static void add(String value) {
        
        if(value != null)
		if(contains(value))
			throw new RuntimeException("添加的数据已存在!");
		else
			addValue(value);
    }

    //判断一个字符串是否在BitSet中是否存在
    public static 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 void main(String [] args) {
        
        String value = "dymdmy2120@gmail.com";

        for(int i = 0; i < seeds.length; i++) {

            func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);

        }

        add(value);

        System.out.println(contains(value));

    }



    /**
     * 哈希类:
     * 	 	一个cap容量(2 << 24),BitSet容量(很大)和选定的一个质数
     * 		通过对应的String value字符串来计算一个对应的hash
     * 
     * @author antsmarth
     *
     */
    static class SimpleHash {
      
    	//容量(DEFAULT_SIZE)
        private int cap;
        //质数
        private int seed;

        public SimpleHash(int cap, int seed) {
            
            this.cap = cap;
            this.seed = seed;

        }

        //计算hash
        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;

        }


    }


}

好啦,对于布隆过滤器我简单的就描述到这

资料

关于布隆过滤器其实有许多大牛已经探究的很深,所以在这我放几个传送门(好资料共享之):