数据结构与算法--位图&布隆过滤器

位图

位图可以看成是一种比较“特殊”的散列表。比如有1千万个整数,要查找某个整数是否在这1千万个整数中,就可以使用位图。

如果整数的范围在1到1亿之间,申请一个大小为1亿、整数类型为布尔类型的数组。将这1亿个整数作为数组下标,将对应到数组值设置成true。比如有整数5就设置array[5] = true。查询某个整数k是否在这1亿个整数中的时候,只需要读取array[k]是否等于true。

不过,很多语言提供的布尔类型大小是1个字节的,实际上,表示true和false两个值只需要一个二进制位。通过位运算用其中的某个位表示某个数字即可:

public class BitMap{ // java中char类型占16bit,2个字节
    private char[] bytes;
    private int nbits;
    
    public BitMap(int nbits){
        this.nbits = nbits;
        this.bytes = new char[nbits / 16 + 1];
    }
    
    public void set(int k){
        if (k > nbits) return;
        int byteIndex = k / 16;
        int bitIndex = k % 16;
        bytes[byteIndex] |= (1 << bitIndex);
    }
    
    public boolean get(int k){
        if (k > nbits) return false;
        int byteIndex = k / 16;
        int bitIndex = k % 16;
        return (bytes[byteIndex] & (1 << bitIndex)) != 0;
    }
}

位图适用于数字所在范围不是很大的情况,如果数字的范围很大不是1到1亿而是1到10亿,那位图的大小消耗的内存空间可能不降反增。布隆过滤器(bloom filter)就是解决这个问题,对位图的内存消耗的优化。

布隆过滤器

布隆过滤器(Bloom Filter)是基于位图这种数据结构的一种该进。

对于数据的范围1到10亿,布隆过滤器仍然使用大小位1亿数组,然后通过k个哈希函数,对同一个数字进行求哈希值。每个哈希值都会将数据映射到数组的某个角标,从而得到K个不同哈希值分别记作X1、X2、X3,...Xk。把这k个数字对应的BitMap[X1],BitMap[X2],BitMap[X3],...,BitMap[Xk]都设置为true。

意味着布隆过滤器用k个二进制位,来表示一个数据是否不存在。

当查询某个数字是否存在的时候,用同样的k个哈希函数,对这个数字求哈希值分别得到Y1、Y2、Y3、...、Yk。再查看这k个哈希值,对应位图中的数值是否都为true。如果都位true则说明这个数字可能存在,如果有其中任意一个不为true,那就说明这个数字必定不存在。

如果有其中任意一个不为true,那就说明这个数字必定不存在:

如果都为true并不能说明这个数字一定存在:

尽管布隆过滤器会存在误判,但这并不影响它发挥大作用。很多场景对误判有一定的容忍度。比如爬虫判重问题,即便一个没有被爬过的网页被误判位已经被爬取,对于搜索引擎来说也是可以容忍的,毕竟网页太多了搜索引擎也不可能100%都爬取到。

如何实现网页爬虫中的URL去重功能?

爬虫的工作原理是,通过解析已经爬取页面中的网页链接,然后再爬取这些链接对应的网页。

同一个网页链接有可能被包含在多个页面中,为了避免重复爬取,所以需要记录已经爬取的网页链接(也就是 URL)到已爬列表,爬取前判断是否已经爬取过。

另外,每爬取一个页面会将解析到的url添加到待爬列表,一个被解析出来的url需要高效的判断是否已经添加到已爬列表被爬取过,还要高效判断是否已经添加到待爬列表中。

为了增大已爬和待爬列表内存极限,可以采用分治的思想,用多台机器(比如 20 台内存是 8GB 的机器)来存储10亿以上的网页链接。

实现爬虫判重列表可以使用散列表或布隆过滤器来实现。

[散列表vs布隆过滤器]

内存消耗对比:

假设要爬取10亿个网页,一个 URL 的平均长度是 64 字节,那单纯存储这10亿个URL,需要大约60GB的内存空间。

散列表必须维持较小的装载因子,用链表法解决冲突问题,还需要存储链表指针。如果将这10亿个URL存储到散列表,需要的内存空间有可能会超过100GB。

但如果用布隆过滤器记录已经爬取过的网页链接,即使用10倍大小(100 亿个二进制位)的位图来存储,也只需要大约1.2GB内存即可。

查询操作耗时对比:

如果散列表基于链表的方法解决冲突问题,链表中的结点在内存中是离散存储的,无法一下子加载到CPU缓存中利用CPU高速缓存。这个操作涉及很多内存数据的读取是内存密集型的操作,而且还需要逐个进行字符串匹配。

而布隆过滤器用多个哈希函数对同一个网页链接进行处理,CPU 只需要将网页链接从内存中读取一次,进行多次哈希计算,这组操作是 CPU 密集型的操作。

所以对于查询操作,布隆过滤器的速度会比散列表快很多。

[布隆过滤器总结]

布隆过滤器非常适合不需要 100% 准确的、允许存在小概率误判的大规模判重场景。除了爬虫网页去重,还有统计大型网站每日UV都可以使用布隆过滤器,对重复访问的用户,进行去重。

布隆过滤器的误判率,主要跟哈希函数的个数、位图的大小有关。往布隆过滤器中不停地加入数据之后,位图中不是 true 的位置就越来越少了,误判率就越来越高了。所以,对于无法事先知道要判重的数据个数的情况,需要支持自动扩容的功能。

当布隆过滤器中,数据个数与位图大小的比例超过某个阈值的时候,就可以重新申请一个新的位图。后面来的新数据,会被放置到新的位图中。不过判断某个数据是否存在时需要查看多个位图,执行效率会降低一些。

各编程语言对位图、布隆过滤器的实现例子,例如Java中的BitSet,Redis中的BitMap,Google的Guava工具包中的BloomFilter。

[思考题]

假设有1亿个整数,数据范围是从1到10亿,如何快速并且省内存地给这1亿个数据从小到大排序?

答:

假设不需要统计每个整数的重复次数,可以申请一个大小为10亿的位图,只需大约128MB内存,按照下标顺序输出位图上为true的下标即可完成排序。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351