利用BloomFilter过滤判断是否存在大集合中

当需要判断一个对象是否在千万级以上的集合时利用BloomFilter是非常有用的。

Bloom Filter是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。Bloom Filter的这种高效是有一定代价的:在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误认为属于这个集合(false positive)。因此,Bloom Filter不适合那些“零错误”的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter通过极少的错误换取了存储空间的极大节省。
即为通过如下checkExists返回为true则不一定在这个集合中返回false一定不在集合中,原理即为给定一个对象之后进行n次hash然后根据每次hash出的值进行在区域里面打点,判定一个对象时hash之后然后打点看下所有打点都存在值是否命中,
即一个人说在集合中不一定在所有人都说在也不一定在,只要有人说不在则一定不在。即为push时把每个对象hash多次,相当于加密,查询存在时将查找的值hash多次,如果多次hash的值扔在集合中则则不一定在集合中,存在冲突的情况,
如果多次hash之后又一次不在集合中则一定不在集合中。
如果想判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定。链表,树等等数据结构都是这种思路. 但是随着集合中元素的增加,我们需要的存储空间越来越大,检索速度也越来越慢。不过世界上还有一种叫作散列表(又叫哈希表,Hash table)的数据结构。它可以通过一个Hash函数将一个元素映射成一个位阵列(Bit Array)中的一个点。这样一来,我们只要看看这个点是不是 1 就知道可以集合中有没有它了。这就是布隆过滤器的基本思想。

Hash面临的问题就是冲突。假设 Hash 函数是良好的,如果我们的位阵列长度为 m 个点,那么如果我们想将冲突率降低到例如 1%, 这个散列表就只能容纳 m/100 个元素。显然这就不叫空间有效了(Space-efficient)。解决方法也简单,就是使用多个 Hash,如果它们有一个说元素不在集合中,那肯定就不在。如果它们都说在,虽然也有一定可能性它们在说谎,不过直觉上判断这种事情的概率是比较低的。
哈希函数的概念是:将任意大小的数据转换成特定大小的数据的函数,转换后的数据称为哈希值或哈希编码。
public class BloomFilter<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    
    protected byte[] flags; //hash 数据
    
    protected int size;     //hash 元素个数
    protected int bitsize;  //hash位数
    
    protected transient Murmur2  hashFunction;
    protected int hashCount; //hash函数个数
    
    protected int[] seeds; //hash 函数seed
    
    /**
     * Returns a new BloomFilter object.
     * 
     * @param hashCount
     *            Number of hash functions.
     * @param size
     *            Bloom filter size <b>in bits</b>. If this parameter is < 8,
     *            then the filter will have the size of 8 bits. Otherwise the
     *            size will be (size / 8) * 8 bits.
     * @param hashCount
     *            An object extending the IntHashFunctionFactory
     *            (Murmur2Factory, for instance) which supplies the hash
     *            functions.
     */
    public BloomFilter(int hashCount, int size) {
        init();
        setupSizes(hashCount, size);
        
    }
    
    public BloomFilter() {
        init();
    }

    public void init() {
        hashFunction = new Murmur2();
    }
    
    /**
     * Returns a size of a filter in <b>bytes</b>
     * 
     * @return Size of the filter in bytes.
     */
    public Integer getSize() {
        return this.size;
    }

    /**
     * Returns a size of a filter in <b>bits</b>
     * 
     * @return Size of the filter in bits
     */
    public Integer getBitSize() {
        return this.bitsize;
    }

    /**
     * Converts hash value into corresponding bit number
     * 
     * @param hash
     *            Int hash value
     * @return Index of corresponding bit
     */
    protected long getIndex(int hash) {
        long hl = (long) hash;
        hl += (long) Integer.MAX_VALUE;
        hl %= bitsize;
        return  hl;
    }

    /**
     * Gets actual flags array index by bit number
     * 
     * @param bigIdx
     *            Bit number
     * @return Corresponding index in flags array
     */
    protected long getBitIndex(long bigIdx) {
        return (bigIdx >> 3);
    }

    /**
     * Gets bit by bit number
     * 
     * @param bigIdx
     *            Bit number
     * @return Bit mask
     */
    protected byte getBitMask(long bigIdx) {
        return (byte) (1 << (bigIdx % 8));
    }

    /**
     * Converts hashes[index] value into index and mask and sets an appropriate
     * bit in flags
     * 
     * @param hashes
     *            Hash values array
     * @param index
     *            Index of current hash in a loop
     */
    protected void doProcessAddElement(int[] hashes, int index) {
        long idx = getIndex(hashes[index]);

        int cidx = (int)getBitIndex(idx);
        byte mask = getBitMask(idx);
        flags[cidx] |= mask;
    }

    /**
     * Converts hashes[index] value into index and mask and tests if an
     * appropriate bit is set
     * 
     * @param hashes
     *            Hash values array
     * @param index
     *            Index of current hash in a loop
     * @return <code>true</code> if corresponding bit is set, otherwise
     *         <code>false</code>
     */
    protected boolean doProcessCheckElement(int[] hashes, int index) {
        long idx = getIndex(hashes[index]);

        int cidx = (int)getBitIndex(idx);
        byte mask = getBitMask(idx);

        return ((flags[cidx] & mask) != 0);
    }

    /**
     * Adds an element to the filter. Hashes are calculated based on the
     * element.toString() value.
     * 
     * 我们去掉了同步代码,这个函数应该在初始化阶段调用, 后续不能再调用
     * 
     * @param element
     *            An element to add
     */
    public void put(T element) {
        int[] h = hash(element.toString().getBytes());
        for (int i = 0; i < hashCount; i++) {
            doProcessAddElement(h, i);
        }
    }

    /**
     * 检查元素是否在集合中,
     * 我们去掉了同步代码, 因此不要与put 混合使用
     * @param element
     * @return
     */
    public boolean checkExists(T element) {
        boolean result = true;
        int[] h = hash(element.toString().getBytes());
        for (int i = 0; i < hashCount; i++) {
            result &= doProcessCheckElement(h, i);
        }
        return result;
    }
    
    public double getQuality(long storageSize) {
        double x = Math.pow(1.0 - 1.0 / (double) bitsize,
                (double) (hashCount * storageSize));
        double q = Math.pow(1 - x, hashCount);

        return 1 - q; // 1 - probability of false positive
    }

    protected int[] hash(byte[] data) {
        int[] result = new int[hashCount];

        for (int i = 0; i < hashCount; i++) {
            if (i == 0)
                result[i] = hashFunction.hash(data, 87914052 ^ seeds[i]);
            else
                result[i] = hashFunction.hash(data, result[i - 1] ^ seeds[i]);                        
        }

        return result;
    }

    protected void setupSizes(int hashCount, int size) {
        if (size >= 8)
            this.size = size >> 3;
        else
            this.size = 1;

        this.bitsize = this.size << 3;
        this.hashCount = hashCount;
        flags = new byte[this.size];
        seeds = new int[hashCount];
        for (int i = 0; i<hashCount; i++)
            seeds[i] = (int)Math.round(Math.random()*1000000)+1;
    }

    public static void main(String[] args) {
        BloomFilter<String> filter = new BloomFilter<String>(10, 200000000);
        System.out.println(filter.getQuality(4200000));
        filter.put("zhangsan");
        filter.put("wangwu");
        System.out.println(filter.checkExists("wangwu"));
        System.out.println(filter.checkExists("wang"));
        
        BloomFilter<String> filter2 = new BloomFilter<String>(10, 100000000);
        System.out.println(filter2.getQuality(2500000));
    }

    
}

public class Murmur2 {        
    /**
     * Returns the Murmur2 (32-bit) hash value of the supplied data.
     *
     * @param  data Data that needs to be hashed in form of byte array.
     * @param seed Seed for hash function      
     * @return Integer hash value 
     */
    public int hash(byte[] data, int seed) {        
        int m = 0x5bd1e995;
        int r = 24;

        int len = data.length;
        int h = seed ^ len;

        int i = 0;
        while (len >= 4) {
            int k = data[i + 0] & 0xFF;
            k |= (data[i + 1] & 0xFF) << 8;
            k |= (data[i + 2] & 0xFF) << 16;
            k |= (data[i + 3] & 0xFF) << 24;

            k *= m;
            k ^= k >>> r;
            k *= m;

            h *= m;
            h ^= k;

            i += 4;
            len -= 4;
        }

        switch (len) {
        case 3:
            h ^= (data[i + 2] & 0xFF) << 16;
        case 2:
            h ^= (data[i + 1] & 0xFF) << 8;
        case 1:
            h ^= (data[i + 0] & 0xFF);
            h *= m;
        }

        h ^= h >>> 13;
        h *= m;
        h ^= h >>> 15;

        return h;
    }

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值