布隆过滤器
前言
前两天, 一个大学同学问我布隆过滤器, 我本想反手甩他一篇我写的文章, 尴尬的是我找了找发现没有写过....
无妨, 补上
场景
你在写一个全网资源的爬虫, 为了爬取全网的资源, 页面中所有的超链接你都要点击去访问一遍. 但是肯定会遇到这种情况, A页面持有B页面的链接, 同时B页面也持有A页面的链接, 你的爬虫会陷入无限循环中. 这个时候去重是必须的, 你需要知道一个url之前有没有访问过, 将访问过的url跳过, 防止重复访问. 问, 如何判断一个链接之前是否访问过.
很简单, 存个集合, 访问过的都扔到里边, 新链接来了, 看看里边有没有就可以了. 好, 来算一笔账, 如果一个链接20个字符长, ASCII编码, 一个字符占一个字节, 那一个链接就是20个字节. 全网如果有10万个链接, 那就需要1.9MB空间(极端情况, 不考虑其他空间). 如果有一亿个链接呢? 1900MB. 很好, 你一个爬虫需要近2G内存空间. 你能不能行.
思考
上面已经看到了, 使用集合来存储链接过于耗费内存, 那么, 有没有较之更好的方法呢?
版本一
回忆一下JAVA中哈希表是如何实现的. 通过hash函数, 将字符串映射为数字, 然后直接放到对应索引的链表上.
既然hash函数能够将链接映射为一个数字, 那我们可以仿照着建一个数组, 每个索引标识一个链接, 这样就不需要存储字符串本身了. 我们准备一个数组, 将对应的索引位标记为1, 没有访问过的则默认为0. 不就可以把长长的字符串空间进行压缩了?
以上是一个数组, 其中每一个内容是一个int. 再来算一下, 原来一个链接20个字节, 现在转换为int是4个字节, 一亿数据存储需要 381.5MB. 相较之前节省了很大一批.
版本二
看了上面数组的形式, 有没有发现一个问题. 每个数字只需要存储0和1. 这二进制1位就能解决的问题, 没必要用int32位啊. 将上面的数组形式转换成位的操作, 其他思路均不变.
再来算一笔账, 一个链接只需要1位存储, 一亿数据存储需要 11.9MB. 哎, 这就能接受了, 完全没有占用多大空间嘛. 直接将原来接近2G的内存消耗, 压缩到了不到12MB.
版本三
别高兴的太早, 还记得哈希函数的一个小问题么? 哈希碰撞. 如果你想将无数个字符串, 映射到有限的n个数字上, 你就算一个一个的放, 也得轮到第二圈了. JAVA中的HashMap是通过数组加链表的形式来处理的.
对于我们的程序, 如何? 其实还好. 一亿条数据, 就算其中有几百几千条数据发生碰撞, 识别的时候被认为已经处理过了, 其实也没什么问题. 但我们还是想要降低这种概率. 来了.
既然一个数字会发生碰撞, 那我一个链接生成10个数字, 你如果10个数字同时碰撞的概率就小得多了吧. 至此, 布隆过滤出来了. 将一个链接, 通过n个不同的hash函数, 生成对应的n个索引, 如果n个索引的值均为1, 则说明存在其中, 否则不在.
如图, 每个字符串生成三个索引, 并将其对应值标记为1.
别高兴的太早, 再看下图:
这个时候又来一个字符串 test
, 计算发现它存在???但其实它不在.如何避免呢? 无法避免. 如果你需要确定的知道它有没有存在, 就只能将它自身进行存储, 在节省空间的时候本身就已经丢失了部分精度. 但是无妨, 我们的爬虫还是能够容忍这种情况的.
介绍完毕, 这就是布隆过滤器了.
完事
看了上面, 布隆的基本概念也齐活了. 布隆的特点如下:
- 说你不在, 你一定不在
- 说你在, 你可能在
其适合于这种允许存在一定误判的场景. 也就是宁可放过一个, 绝不错杀一千的场景.
看了布隆过滤器, 其涉及的大小只有两个, 1. 数组的大小. 2. hash函数的个数. 而选取合适的值就可以尽量的降低误判概率. 涉及高深的数学领域, 咱也不太懂.