《自己动手写网络爬虫》笔记6-使用布隆过滤器实现Visited表

在企业级搜索引擎中,常用一个称为布隆过滤器(Bloom Filter)的算法来实现对已经抓取过的URL进行过滤。


布隆过滤器算法

我们经常要判断一个元素是否在一个集合里面,最直接的方法是将集合中的全部元素存储在计算机中,遇到一个新元素时,将它和集合中的元素直接比较。一般来讲,计算机中的集合是用哈希表(Hash Table)来存储的。它的好处是快速而准确,缺点是浪费存储空间。当集合比较小的时候,这个问题不显著,但是当集合巨大时,哈希表存储效率低的问题就显现出来了。
例如一个公众电子邮件的提供商总是需要过滤来自发件人的垃圾邮件。一个办法是记录下那些发垃圾的Email的地址。由于那些发件人不停地在注册新的地址,全世界至少 也有几十亿个发垃圾邮件的地址,将他们都存储起来需要大量的网路服务器。如果用哈希表(将每一个email地址对应成一个8字节的信息指纹,然后存入哈希表。由于哈希表存户效率一般只有50%,因此一个email地址需要占用16个字节,一亿个地址大约需要1.6GB )。因此存储几十亿个邮件地址可能需要几百个GB放入内存。
但是布隆过滤器只需要哈希表的1/8到1/4的大小就能够解决同样的问题。

算法描述

布隆过滤器是有巴顿·布隆于1970年提出来的,它实际上是一个很长的二进制向量和一系列随机映射函数。

建立过滤器

假设存储一亿个电子邮件的地址,需建立一个16亿二进制常量,即2亿字节的向量,然后将这个16亿个二进制位全部设置为0,对于每一个电子邮件地址X,用8个不同的随机数产生器(F1,F2,……F8)产生8个信息指纹(f1,f2,…….f8)。再用一个随机数产生器G把这8个信息指纹映射到1到16亿中的8个自然数g1,g2….g8。现在把这个8个位置的二进制位全部设为1。当我们对这个1亿个email地址都进行这样的处理后,一个针对这些email地址的布隆过滤器就建成了。

过滤器工作

对于电子邮件地址Y,我们用8个随机数产生器(F1,F2,……F8)对这个地址信息产生8个信息指纹S1,S2,….S8。然后将这8个指纹对应到布隆过滤器的8个二进制位,分别是T1,T2,…T8。如果Y在黑名单中,显然T1,T2,…..T8对用的8个二进制位一定是1,,这样在遇到任何黑名单中的电子邮件地址时,我们都能狗进行准确的判断。

不足之处

布隆过滤器会识别出所有在黑名单中的可疑地址,但是他也有极小的可能将一个不再黑名单中的邮件地址判定为黑名单,这种可能性很小。我们将它称之为误识概率。常见的补救办法是建立一个小的白名单,存储那些可能误判的邮件地址。

布隆过滤器Java实现

书上没有说所实现的接口visitedFrontier里面包含那几个函数,我也不能具体吧这个接口写出来,反正,不用继承接口,我们认为这是内部函数这也是一样的。
然后我将原来main函数里面的内容重新写了一个函数,然后在MAIN.main()里面调用,这样便于修改。

public class SimpleBloomFilter  {
    private static final int DEFAULT_SIZE = 2 << 24;
    private static final int[] seeds = new int[]{7, 11, 13, 31, 37, 61};
    private BitSet bits = new BitSet(DEFAULT_SIZE);
    private SimpleHash[] func = new SimpleHash[seeds.length];

    public SimpleBloomFilter() {
        for (int i = 0; i < seeds.length; i++) {
            func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);
        }
    }
    public void start(){
        String value = "stone2083@yahoo.cn";
        SimpleBloomFilter filter = new SimpleBloomFilter();
        System.out.println(filter.contains(value));
        filter.add(value);
        System.out.println(filter.contains(value));
    }
    //覆盖方法,把URL添加进来
    public void add(CrawlUrl value) {
        if (value != null) add(value.getOriUrl());
    }

    private void add(String value) {
        for (SimpleHash f : func) {
            bits.set(f.hash(value), true);
        }
    }

    public boolean contains(CrawlUrl value) {
        return contains(value.getOriUrl());
    }

    private 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 class SimpleHash {
        private int cap;
        private int seed;

        public SimpleHash(int cap, int seed) {
            this.cap = cap;
            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 (cap - 1) & result;
        }
    }
}

输出结果
这里写图片描述

布隆过滤器误判率表

比率(items:bits)误判率(False-positive)
1:10.63212055882856
1:20.39957640089373
1:40.14689159766038
1:80.02157714146322
1:160.00046557303372
1:320.00000021167340
1:640.00000000000004

比较保守的实现是为每个URL分配4个字节,项目和位数的比是1:32,误判率是0.00000021167340。对于5000万数量级的URL,布隆过滤器只占用200MB,并且排重速度超快,一遍下来不到两分钟。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的Python实现布隆过滤器的例子: ```python import mmh3 from bitarray import bitarray class BloomFilter: def __init__(self, size, hash_count): self.size = size self.hash_count = hash_count self.bit_array = bitarray(size) self.bit_array.setall(0) def add(self, string): for seed in range(self.hash_count): result = mmh3.hash(string, seed) % self.size self.bit_array[result] = 1 def lookup(self, string): for seed in range(self.hash_count): result = mmh3.hash(string, seed) % self.size if self.bit_array[result] == 0: return "Nope" return "Probably" ``` 上面的代码中,我们用到了 `mmh3` 和 `bitarray` 两个包。 `mmh3` 包用来生成hash值, `bitarray` 包用来创建位数组。在 `__init__` 方法中,我们传入两个参数 `size` 和 `hash_count`,分别代位数组大小和hash函数个数。在 `add` 方法中,我们为每个字符串生成 `hash_count` 个hash值,并将对应的位数组位置设为1。在 `lookup` 方法中,我们同样为字符串生成 `hash_count` 个hash值,并检查对应的位数组位置是否都为1,如果有任何一个位置为0,就说明该字符串不在集合中。 下面是一个例子,演示如何使用布隆过滤器: ```python bf = BloomFilter(500000, 7) words = ['hello', 'world', 'foo', 'bar', 'baz'] for word in words: bf.add(word) print(bf.lookup('hello')) # Probably print(bf.lookup('foobar')) # Nope ``` 在上面的例子中,我们创建了一个布隆过滤器,设置位数组大小为500000,hash函数个数为7。然后,我们将一些单词添加到集合中,并使用 `lookup` 方法检查某些单词是否在集合中。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值