水煮Redisson(五)-Bloom过滤器简介

135 阅读3分钟

什么是布隆过滤器

布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

原理

平时我们判断一个元素是否在集合中,要么使用list.contains(o),要么使用hashmap.containsKey(o),其中hashmap的查询效率比较高,是以空间换时间的方式,代价比较大。这里介绍的布隆过滤器比较特殊,在布隆过滤器之中查询一个元素,有两个结果:可能存在和一定不存在,这里涉及到了概率,业务使用的时候需要注意;
下面的例子中,我们先写入了Tom,根据计算出来的hashcode【84274】,将集合中相应的index设置为1,其中index为2,4,7,8的位置被设置为1:
[0, 0, 1, 0, 1, 0, 0, 1, 1, 0]
再来看看Jim,hashcode是74478,涉及的index是4,7,8;是Tom的hashcode的子集,所以查询的时候也返回了true。
最后来看看Apple,hashcode是63476538,涉及的index是3,4,5,6,7,8;与Tom的index存在差集,所以查询的时候返回false,表示此元素一定不存在;
实际使用的时候,需要根据元素的数量级设置集合的长度,本质上来说,集合长度越长,那么准确度越高。

简单Java实现

public static void main(String[] args) {
    byte[] bytes = new byte[10];
    String name = "Tom";
    System.out.println("hashcode," + name + ":" + name.hashCode());
    offer(bytes, name);
    System.out.println("isExists," + name + ":" + isExists(bytes, name));
    name = "Jim";
    System.out.println("hashcode," + name + ":" + name.hashCode());
    System.out.println("isExists," + name + ":" + isExists(bytes, name));
    name = "Apple";
    System.out.println("hashcode," + name + ":" + name.hashCode());
    System.out.println("isExists," + name + ":" + isExists(bytes, name));
}

/**
 * 写入
 *
 * @param bytes 集合
 * @param data  元素
 */
private static void offer(byte[] bytes, String data) {
    int code = data.hashCode();
    String value = String.valueOf(code);
    char[] chars = value.toCharArray();
    for (char c : chars) {
        bytes[Integer.parseInt(c + "")] = 1;
    }
    System.out.println("offer [" + data + "],bits:" + Arrays.toString(bytes));
}

/**
 * 查询,false:肯定不存在,true:不一定存在
 *
 * @param bytes 集合
 * @param data  元素
 * @return bool
 */
private static boolean isExists(byte[] bytes, String data) {
    int code = data.hashCode();
    String value = String.valueOf(code);
    char[] chars = value.toCharArray();
    for (char c : chars) {
        byte temp = bytes[Integer.parseInt(c + "")];
        if (temp == 0) {
            return false;
        }
    }
    return true;
}

控制台输出

hashcode,Tom:84274
offer [Tom],bits:[0, 0, 1, 0, 1, 0, 0, 1, 1, 0]
isExists,Tom:true
hashcode,Jim:74478
isExists,Jim:true
hashcode,Apple:63476538
isExists,Apple:false

可以看出,我们只写入了Tom,查询Tom时,返回true,查询Jim时,也返回了true,但是Jim其实是不存在的;我们再查询Apple时,返回了false,这也就是说,Jim不一定存在,Apple肯定不存在。

删除的问题

元素写入之后,最好不要删除,有概率会发生误删。有人说可以使用记数法来删除,每插入一个元素相应的index计数器加1,这样删除元素的时候将对应的计数器减掉就可以了。但是这里有个致命的问题:你无法确定要删除的元素是否一定存在。也就是说,你写入了Tom,但是你要删除Jim,这时Jim查询结果是存在【其实不存在】,然后你删掉了Jim。你再去查询Tom的时候,发现Tom也不存在了,这就是误删的问题所在。