Java实现简单的BloomFilter

201 阅读3分钟

前言

bloomfilter ,布隆过滤器是一个判断是否存在某个 Key 的简单的数据结构。他存在一定的误判性,但是概率较低,甚至这个概率是可控的

实现原理

类似这种是否的问题其实都可以使用 1 位表示,比如 1 就是存在,0 就是不存在,这样一个字节就可以表示 8 个数据是否存在。但是如何精确的表示当前位呢?如果是 int 类的数据,则可以使用一个 bit 数组,数据大小位 int 的最大值,然后将当前的 int 值直接写入到对应的 index 中。如果是 char,String 类型的呢?可以使用对应的 hashcode,然后取模获取到 index。

位图在表示可能存在的 int 的时候也就是最多需要 2 的 32 个 bit,每一个字节是 8 位,也就是说需要 2^32/(810241024) = 2^9,大概需要 512MB。如果使用固定长度的数组,然后取余获取位置,则需要的内存变少,而且误判概率较高,这也是 hashmap 的基本原理,先找到 index,如果冲突转链表。

位图可以解决很多的问题,但是如果数组的长度取的太小,则冲突的可能性就越大,误判率就高。和数据的最大最小值成正相关。为了更有效的使用数组空间,就有了布隆过滤器。他解决了位图在需要准确度高的时候需要很大空间和小空间冲突可能性大的问题。

解决的办法很简单,取 key 的多个 hash 值,然后和当前的数据长度取余。因为每个 hash 算法最后的 hashcode 结果都不一样,所以取余对应的 index 数量也变多,相当于记录每一个数据不再使用一位而是使用多位。假设当前有 k 个 hash 算法,最后需要设置 m 位标识一个 key 值(m>0 && m<=k)。也就是每一个 Key 最后会被写入到当前数组的 m 位上。

1652302-20200510160050474-1143514940.png

使用上面的方式很明显会出现一定概率的误判,比如前取 m=3,如果当前 key 的 index 分别位 1,2,3。可能存在 key1,key2,key100, 刚好在 k 中的某一位占用了 1,2,3。如果先写入 Key 在查询,则没有问题,但是如果先查询,那么可能会得到一个错误的结果,就是不存在的数据却返回了存在。

Bloom filter 里面有 k 个哈希函数, m 个比特, 以及 n 个已插入元素。错误率会近似于 (1-e^(-kn/m))^k, 先确定可能插入的数据集的容量大小 n, 然后再调整 k 和 m 来为你的应用配置过滤器最有的 k 值 最优的 k 值: (m/n)ln(2) ln(2) ~= 0.69

但是如果一个值查询都 m 位中的某个值是 0,那么他一定不存在。

Java 实现简单的布隆过滤器

注意的是,上面的 hash 算法并没有得到完全的认证,是我自己到处找改改搞来的。实现的也不是很严谨,只是举个简单的例子实现下布隆过滤器的功能。可以的优化的点很多,solts 可以不是 int 数组,毕竟一个 int 使用了 4 个字节。

public class BloomFilter {

    private int[] solts;

    private int k;

    private int n;
    //private double miss;
    private List<Hash> hashAlogSet=new ArrayList<Hash>();

    private Hash first;

    private int m = 0 ;
    private int size;

    public BloomFilter(int m,int n){
        loadHash();
        this.m=m==0?3:m;
        k= (int) (m * 0.69);
        if(k==0){
            k=m;
        }
        if(k>hashAlogSet.size()){
            k=hashAlogSet.size();
        }

        this.n=n;
        this.size=0;
        this.solts=new int[n];
    }

    public boolean put(byte[] num){
        this.size++;

        for(int i = 0 ; i < k ; i ++){
            Hash curHash=hashAlogSet.get(i);
            int soltIndex=curHash.hash(num)%n;
            if(soltIndex<0){
              soltIndex= (Integer.MAX_VALUE+soltIndex)%n;
            }
            solts[soltIndex]=1;
        }
        return true;
    }

    public boolean contains(byte[] num){
        for(int i = 0 ; i < k ; i ++){
            Hash curHash=hashAlogSet.get(i);
            int soltIndex=curHash.hash(num)%n;
            if(soltIndex<0){
                soltIndex= (Integer.MAX_VALUE+soltIndex)%n;
            }
            if(solts[soltIndex]==0){
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        BloomFilter bloomFilter=new BloomFilter(10,100);
        bloomFilter.put("hello".getBytes());
        bloomFilter.put("asdasd".getBytes());
        bloomFilter.put("zxcasd".getBytes());
        bloomFilter.put("cxzczxc1".getBytes());
        bloomFilter.put("asdcasda".getBytes());
        bloomFilter.put("23123asdf".getBytes());
        assert bloomFilter.contains("hello".getBytes()) ;
        assert !bloomFilter.contains("hello5".getBytes());
    }

    public void loadHash(){
        hashAlogSet.add(new Hash() {
            @Override
            public int hash(byte[] key) {
                return bkdr(key);
            }
            int bkdr(byte[] data) { //《The C Programming Language》
                final int seed = 131; // 31 131 1313 13131 131313 etc..
                int hash = 0;

                for (byte b : data) {
                    hash = (hash * seed) + (b & 0xFF);
                }

                return hash;
            }

        });
        hashAlogSet.add(new Hash() {

            @Override
            public int hash(byte[] key) {
                    return fnv1a(key);
            }
            int fnv1a(byte[] data) { //http://www.isthe.com/chongo/tech/comp/fnv/#FNV-1a
                final int prime = 0x01000193;
                int hash = 0x811C9DC5;

                for (byte b : data) {
                    hash ^= b;
                    hash *= prime;
                }

                return hash;
            }
        });

        hashAlogSet.add(new Hash() {

            @Override
            public int hash(byte[] key) {
                return djb(key);
            }
            int djb(byte[] data) { //http://www.cse.yorku.ca/~oz/hash.html
                int hash = 5381;

                for (byte b : data) {
                    hash = ((hash << 5) + hash) + (b & 0xFF);
                }

                return hash;
            }
        });

        hashAlogSet.add(new Hash() {

            @Override
            public int hash(byte[] key) {
                return murmur3_32(key,1234);
            }
            int murmur3_32(byte[] data, int seed) { // google https://samtools.github.io/htsjdk/javadoc/htsjdk/htsjdk/samtools/util/Murmur3.html
                final int c1 = 0xcc9e2d51;
                final int c2 = 0x1b873593;
                final int r1 = 15;
                final int r2 = 13;
                final int m = 5;
                final int n = 0xe6546b64;

                int hash = seed;
                int len = data.length;
                int currentIndex = 0;

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

                    k *= c1;
                    k = (k << r1) | (k >>> (32 - r1));
                    k *= c2;

                    hash ^= k;
                    hash = (hash << r2) | (hash >>> (32 - r2));
                    hash = hash * m + n;

                    len -= 4;
                }

                switch (len) {
                    case 3:
                        hash ^= (data[currentIndex + 2] & 0xFF) << 16;
                    case 2:
                        hash ^= (data[currentIndex + 1] & 0xFF) << 8;
                    case 1:
                        hash ^= (data[currentIndex] & 0xFF);
                        hash *= c1;
                        hash = (hash << r1) | (hash >>> (32 - r1));
                        hash *= c2;
                }

                hash ^= data.length;
                hash ^= (hash >>> 16);
                hash *= 0x85ebca6b;
                hash ^= (hash >>> 13);
                hash *= 0xc2b2ae35;
                hash ^= (hash >>> 16);

                return hash;
            }
        });
    }

    interface Hash{
        int hash(byte[] key);
    }

}


总结

实现了一个简单的布隆过滤器。没有考虑很多,run 就行的版本。水一篇文章-_-