再看不懂BitMap算法,我请你吃饭(五)

通过一个小故事认识布隆过滤器(Bloom Filter)

某男生着几个朋友去KTV唱歌,他出示自己的身份证,KTV管理员根据身份证号“计算”出3个房间号,这伙人只能去这些房间,把灯都打开,开始K歌。(这里的“3”是假设,可能会调整)

再有客人来,KTV管理员重复上述操作。每次“计算”出来的3个房间号,几乎不可能重复,言外之意,可能重复,只是几率极低。而且,每次计算的结果都一样。如果不幸重复了,那也只能将就了。

有个女孩心急火燎地来寻找她男朋友,由于房间太多,得有数亿间(此处有夸张),逐个去查看几乎不可能。KTV管理员向她索要了她男朋友的身份证号,“计算”出3号房间号,并查看一个“神秘仪表盘”,如果房间有人,即亮灯着,通过“神秘仪表盘”一眼就能看出来。但KTV管理员对她说,如果3个房间号的灯都亮着,则她男朋友可能就在其中,也可能不在,需要她去核实下。如果3个房间的灯有一个或多个是熄灭的,她男朋友肯定不在这里,只能去别处再找找了。

布隆过滤器介绍

布隆过滤器(Bloom Filter)算法是由BURTON H. BLOOM于1970年提出的,论文见这里。主要用于解决这类问题:某个元素是否存在于一个很大很大很大的集合中。如果集合里的元素很少,方法就比较多了,所以这里强调了集合中已存在的元素个数非常多,可以简单理解为成百上千亿个,集合大小记为n。它的优点是占用的内存较少,查询时间较快,缺点是有一定的误判率,记为p,某些情况下不确定某个元素是否存在,进一步,要想删除该元素也就比较困难了。

简单地讲其实现算法:

  • 初始化一个空的bit数组,数组的长度记为m,可以形象地理解为上述故事中的若干KTV房间;
  • 初始化k个哈希函数,可以形象地理解为上述故事中的“计算”算法;
  • 遍历成百上千亿个元素,对每个元素计算k个哈希值,并“添加到”bit数组中,可以形象地理解为上述故事中的一伙人进入KTV房间;
  • 至此,准备工作已完毕;
  • 当有人问“某元素”是否存在于那个bit数组中时,计算该元素的k个哈希值,判断这k个值是否存在于bit数组中:若都存在则该元素可能存在于bit数组中,言外之意,也可能不存在;若有1或多个值不存在,则该元素肯定不存在于bit数组中。可以形象地理解为上述故事中的女孩寻找男友;
  • 一图胜千言:下图一分别对x、y、z计算3个Hash值,并添加到bit数组中。想判断w是否存在于bit数组中,先计算w对应的3个哈希值,这3个值有一个不存在于bit数组中,结论是:w不存在。
  • 你品,你仔细品,是不是和该系列前述博客中提到的BitMap算法有相关性啊。这也是把布隆过滤和BitMap算法放在一起讲的原因。

在这里插入图片描述

直接上公式:
在这里插入图片描述
在这里插入图片描述

举个例子

不安全网页的黑名单已经被识别和收集了100亿个,即n等于100亿。每个网页的URL最多占用64字节。现在想要实现一种网页过滤系统,可以根据网页的URL判断该网站是否在黑名单上。要求该系统允许有万分之一以下的判断失误率,即p等于0.0001,并且使用的内存空间不要超过30G。

如果使用Set<String>存储,至少需要内存空间:100*10^8 × 64 ÷ 1024 ÷ 1024 ÷ 1024 ≈ 596G,显然,不满足要求。

套用上述公式,使用手机里的超级计数器(还不会使用LaTex数学公式:-),可算得:m=19.17*n,注意:m是没有单位的。由于代码中要创建一个大小为m的bit数组,该数组至少需要内存空间:19.17*100*10^8 ÷ 8 ÷ 1024 ÷ 1024 ÷ 1024 ≈ 22.32G,注意:除8是将bit先转换为byte。显然,满足不超过30G的要求。

k = ln(2) × 19.17*n ÷ n ≈ 13.29 ≈ 14,因为k表示哈希函数的个数,只能“天花板”取整数了。

隆过滤器的真实失误率,对入门者来说可以暂且不管,此处略。

其他类似使用场景

字处理软件中,需要检查一个英语单词是否拼写正确。据不完全统计,英语单词有100万个左右。传统思路,把所有单词录入到一个嵌入式数据库表里,建立索引。查询用户输入的拼写,查到了表明拼写正确,查不到表明拼写错误。

一个身份证号是否存在于全国失信人员数据库里。

在网络爬虫里,一个网址是否被爬取过。

某个手机号是否被标记为骚扰手机号。

代码实现
import java.util.BitSet;

/**
 * 改编自:https://baike.baidu.com/item/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8/5384697
 */
public class MyBloomFilter {

    /**
     * 约为10亿
     */
    private static final int DEFAULT_SIZE = 256 << 22;

    /**
     * 为了降低错误率,使用加法hash算法,所以定义一个8个元素的质数数组
     * 实际应用中,该数组的个数应该计算出来,此处简单设为8
     */
    private static final int[] seeds = {3, 5, 7, 11, 13, 31, 37, 61};

    /**
     * 相当于构建 8 个不同的hash算法
     * 实际应用中,该数组的个数,应该计算出来
     */
    private static HashFunction[] functions = new HashFunction[seeds.length];

    /**
     * 初始化布隆过滤器
     */
    private static BitSet bitset = new BitSet(DEFAULT_SIZE);

    /**
     * 添加数据
     * 注意:此处和一般BitMap应用不同,虽然仅添加一个数,但却“点亮了8盏灯”
     */
    public static void add(String value) {
        for (HashFunction f : functions) {
            bitset.set(f.hash(value));
        }
    }

    /**
     * 判断相应元素是否存在
     * 若每个Hash值在BitSet中都存在,则该元素可能存在,也可能不存在
     * 若每个Hash值,有一个或多个不存在于BitSet中,则该元素肯定不存在
     */
    public static String contains(String value) {
        for (HashFunction f : functions) {
            boolean b = bitset.get(f.hash(value));
            if (!b) {
                return "肯定不存在:" + value;
            }
        }

        return "可能存在,也可能不存在:" + value;
    }
    
    public static void main(String[] args) {
        for (int i = 0; i < seeds.length; i++) {
            functions[i] = new HashFunction(DEFAULT_SIZE, seeds[i]);
        }

        for (int i = 0; i < (1 * 10^8); i++) {
            add(String.valueOf(i));
        }

        String id = "123456789";
        add(id);

        System.out.println(contains(id));
        System.out.println(contains("234567890"));
    }
}

class HashFunction {

    /**
     * 样本数,通常很大
     */
    private int size;

    /**
     * 盐
     */
    private int seed;

    public HashFunction(int size, int seed) {
        this.size = size;
        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;
    }
}

除了使用java.util.BitSet,当然也可以使用Google发布的EWAHCompressedBitmap,或者Guava组件中的BloomFilter类,或者Redis(注:需安装Redis插件rebloom)。Redis的实现,可以参考这篇博客及源码。

缺点&不足
  • 有一定误判率
其他

看完这些内容,再读《数学之美》之“布隆过滤器”章节就很容易了。
我又看了一遍,我觉得我写的比他通俗易懂:-

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值