QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1450|回复: 0
打印 上一主题 下一主题

[其他经验] 【转】BloomFilter——大规模数据处理利器

[复制链接]
字体大小: 正常 放大

86

主题

13

听众

160

积分

升级  30%

  • TA的每日心情

    2016-4-25 17:12
  • 签到天数: 22 天

    [LV.4]偶尔看看III

    自我介绍
    萌萌哒

    社区QQ达人

    群组2015国赛优秀论文解析

    群组2015年国赛优秀论文解

    跳转到指定楼层
    1#
    发表于 2016-4-8 12:03 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    那些优雅的数据结构(1) : BloomFilter——大规模数据处理利器Posted on 2011-01-02 19:08 苍梧 阅读(37504) 评论(25) 编辑 收藏
      ~6 c& P4 A. E7 u  _: l6 W; D# v
    BloomFilter——大规模数据处理利器
      Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法。通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合。
    ( _& L3 |# _/ ]$ ?9 V# j* N0 @9 Q! `
    . 实例
      为了说明Bloom Filter存在的重要意义,举一个实例:
      假设要你写一个网络蜘蛛(web crawler)。由于网络间的链接错综复杂,蜘蛛在网络间爬行很可能会形成“环”。为了避免形成“环”,就需要知道蜘蛛已经访问过那些URL。给一个URL,怎样知道蜘蛛是否已经访问过呢?稍微想想,就会有如下几种方案:
      1. 将访问过的URL保存到数据库。
      2. 用HashSet将访问过的URL保存起来。那只需接近O(1)的代价就可以查到一个URL是否被访问过了。
      3. URL经过MD5或SHA-1等单向哈希后再保存到HashSet或数据库。
      4. Bit-Map方法。建立一个BitSet,将每个URL经过一个哈希函数映射到某一位。
      方法1~3都是将访问过的URL完整保存,方法4则只标记URL的一个映射位。

    8 T( P/ j* ^) N- w8 [6 X& I
      以上方法在数据量较小的情况下都能完美解决问题,但是当数据量变得非常庞大时问题就来了。
      方法1的缺点:数据量变得非常庞大后关系型数据库查询的效率会变得很低。而且每来一个URL就启动一次数据库查询是不是太小题大做了?
      方法2的缺点:太消耗内存。随着URL的增多,占用的内存会越来越多。就算只有1亿个URL,每个URL只算50个字符,就需要5GB内存。
      方法3:由于字符串经过MD5处理后的信息摘要长度只有128Bit,SHA-1处理后也只有160Bit,因此方法3比方法2节省了好几倍的内存。
      方法4消耗内存是相对较少的,但缺点是单一哈希函数发生冲突的概率太高。还记得数据结构课上学过的Hash表冲突的各种解决方法么?若要降低冲突发生的概率到1%,就要将BitSet的长度设置为URL个数的100倍。
      实质上上面的算法都忽略了一个重要的隐含条件:允许小概率的出错,不一定要100%准确!也就是说少量url实际上没有没网络蜘蛛访问,而将它们错判为已访问的代价是很小的——大不了少抓几个网页呗。
    + q% L' X/ A) J% {
    . Bloom Filter的算法
      废话说到这里,下面引入本篇的主角——Bloom Filter。其实上面方法4的思想已经很接近Bloom Filter了。方法四的致命缺点是冲突概率高,为了降低冲突的概念,Bloom Filter使用了多个哈希函数,而不是一个。
        Bloom Filter算法如下:
        创建一个m位BitSet,先将所有位初始化为0,然后选择k个不同的哈希函数。第i个哈希函数对字符串str哈希的结果记为h(i,str),且h(i,str)的范围是0到m-1 。

    2 E! l5 g/ r+ e4 r# }* I8 G% C
    (1) 加入字符串过程
      下面是每个字符串处理的过程,首先是将字符串str“记录”到BitSet中的过程:
      对于字符串str,分别计算h(1,str),h(2,str)…… h(k,str)。然后将BitSet的第h(1,str)、h(2,str)…… h(k,str)位设为1。
      图1.Bloom Filter加入字符串过程
      很简单吧?这样就将字符串str映射到BitSet中的k个二进制位了。

    6 Y4 w3 t0 k# D7 x0 W/ `) m
    (2) 检查字符串是否存在的过程
    5 N9 d  i3 i% N; c( ^5 }3 Q( X3 F
      下面是检查字符串str是否被BitSet记录过的过程:
      对于字符串str,分别计算h(1,str),h(2,str)…… h(k,str)。然后检查BitSet的第h(1,str)、h(2,str)…… h(k,str)位是否为1,若其中任何一位不为1则可以判定str一定没有被记录过。若全部位都是1,则“认为”字符串str存在。
    - X/ ]! U" T1 g5 k( {4 I
      若一个字符串对应的Bit不全为1,则可以肯定该字符串一定没有被Bloom Filter记录过。(这是显然的,因为字符串被记录过,其对应的二进制位肯定全部被设为1了)
      但是若一个字符串对应的Bit全为1,实际上是不能100%的肯定该字符串被Bloom Filter记录过的。(因为有可能该字符串的所有位都刚好是被其他字符串所对应)这种将该字符串划分错的情况,称为false positive 。
    ) g. f* E* h3 g0 Q: @, f7 }
    (3) 删除字符串过程
       字符串加入了就被不能删除了,因为删除会影响到其他字符串。实在需要删除字符串的可以使用Counting bloomfilter(CBF),这是一种基本Bloom Filter的变体,CBF将基本Bloom Filter每一个Bit改为一个计数器,这样就可以实现删除字符串的功能了。
    + v$ X- p3 ]7 A* ]1 h' i2 T: ?6 ?6 |
      Bloom Filter跟单哈希函数Bit-Map不同之处在于:Bloom Filter使用了k个哈希函数,每个字符串跟k个bit对应。从而降低了冲突的概率。

      l' {# L0 w0 e0 l
    . Bloom Filter参数选择

    5 u( U# b4 y- T- @4 J' N% R
       (1)哈希函数选择
         哈希函数的选择对性能的影响应该是很大的,一个好的哈希函数要能近似等概率的将字符串映射到各个Bit。选择k个不同的哈希函数比较麻烦,一种简单的方法是选择一个哈希函数,然后送入k个不同的参数。
       (2)Bit数组大小选择
         哈希函数个数k、位数组大小m、加入的字符串数量n的关系可以参考参考文献1。该文献证明了对于给定的m、n,当 k = ln(2)* m/n 时出错的概率是最小的。
         同时该文献还给出特定的k,m,n的出错概率。例如:根据参考文献1,哈希函数个数k取10,位数组大小m设为字符串个数n的20倍时,false positive发生的概率是0.0000889 ,这个概率基本能满足网络爬虫的需求了。  
    ! ]6 E$ U6 c5 l/ Z& e5 {
    . Bloom Filter实现代码
        下面给出一个简单的Bloom Filter的Java实现代码:
    1 b# o2 p. ~) X& \+ G2 |
    [url=][/url]
    , l1 }' f: Y1 U% [' [$ _import java.util.BitSet;+ |# h5 {5 t0 O

    4 F5 D& `2 [2 Z: @, ppublicclass BloomFilter ( E$ Z: ?5 u# e0 g
    {0 ^2 k- Z0 \( V0 ]5 P; H
    /* BitSet初始分配2^24个bit */ " x, E, F- X# |% ^9 _, k! [6 i
    privatestaticfinalint DEFAULT_SIZE =1<<25; 3 c+ [( Q2 |  m
    /* 不同哈希函数的种子,一般应取质数 */
    3 v1 M3 c& q2 k! ^4 jprivatestaticfinalint[] seeds =newint[] { 5, 7, 11, 13, 31, 37, 61 };$ S( W1 Q$ i: k) l, @7 ^) Y4 X$ H
    private BitSet bits =new BitSet(DEFAULT_SIZE);
    ) |  G( b$ x- E0 a& _# U( Q+ c/* 哈希函数对象 */
    ; t/ X% j; |1 d; [private SimpleHash[] func =new SimpleHash[seeds.length];
    0 {- ]3 Y$ j; l: m# R, M! d/ a2 [( D( T8 [
    public BloomFilter()
      ]+ a# V2 U% p! P( c7 F1 C% k{2 ?4 M" a) B8 }$ W( u+ c# l
    for (int i =0; i < seeds.length; i++)' R) }) N, R& Q2 |: F: l" H% Q- z
    {
    4 ]& H$ U( \7 j3 I' ^func =new SimpleHash(DEFAULT_SIZE, seeds);
    8 y: D. |7 x& u4 X8 p# J7 ?9 x+ x}
    6 L/ @, m% u& I0 D  d}
    $ B; D9 |: s' O- Y# d7 J% Q, R* |) A2 y7 Y) ~# j2 ]3 g1 Y
    // 将字符串标记到bits中
    : ^) t4 q+ }4 z6 g( }publicvoid add(String value)
      b+ v  a$ Y. `  I4 R{; _  F, S% P, w/ c
    for (SimpleHash f : func)
    ( y1 C- D* u$ S% \{8 A/ q8 ?2 t8 n8 q& I
    bits.set(f.hash(value), true);
    ; m8 M8 ^: e7 D9 u8 m}
    4 q4 @* J/ y/ ]% b; A* J, Z6 g}8 A0 S* g0 C6 T0 \, f+ O$ V
    , y, c# b: f9 t, `/ F0 C
    //判断字符串是否已经被bits标记
    ( y& s# M- a; H' F' f$ Q. vpublicboolean contains(String value)
    8 ^/ Q! [1 D. X0 R; F: m{6 _: W" m0 c! o2 h4 t+ y
    if (value ==null) 3 I5 X. Q& n6 ]4 v' E4 Z
    {" n, h( G) [1 Q0 O: p  T
    returnfalse;
    + b: @* H: {  w6 z}% m- M; u! T$ A. y- B
    boolean ret =true;
    1 p& w  H0 L$ ofor (SimpleHash f : func)
    , V6 _2 k. r( K{' t7 k& y9 S/ n: m6 ?0 w) Q; K0 Q, W
    ret = ret && bits.get(f.hash(value));6 s& n0 W: [, p  C2 b& a; O
    }) h! T; @# k8 |$ B4 y9 m" ]* _
    return ret;
    . m; o, y* C6 L}+ [- }  ?5 `7 a2 x3 F9 M

    6 F: w9 e- e3 l/* 哈希函数类 */
    / |. ~, n8 L3 S" }1 U( jpublicstaticclass SimpleHash & V, \% E+ N7 T/ @( k; V* L1 {0 g
    {, m# w, X2 w% u( P
    privateint cap;8 l9 c. O& @' k1 |
    privateint seed;
    # z* ]/ P6 ^2 a% D: d( t+ f3 T
    & b" J- E+ }9 e# Fpublic SimpleHash(int cap, int seed) ) X- d  p6 \$ j
    {! _4 X% }# `; P& F8 _! X' {
    this.cap = cap;% Z2 ~0 s0 Z4 A8 z
    this.seed = seed;7 V, Z* l' I) O# m
    }; j3 i/ H0 r" t: ?# U$ g

    3 X7 Q" s# r: J+ ]//hash函数,采用简单的加权和hash
    ; B6 I1 T/ t5 dpublicint hash(String value) 0 P3 k' b% }$ T( J
    {
    $ Y9 I1 I0 q, m( f( vint result =0;
    $ a) @, \0 @2 z) Y0 Y0 |int len = value.length();$ ~8 ~! R, N$ @$ o7 [
    for (int i =0; i < len; i++)
    : q9 O8 r5 F  b" T7 V( z& w* j{
    + h* ]2 F1 _  g% q& s+ m, L8 ?result = seed * result + value.charAt(i);- E2 M2 o6 b9 [1 h) ]: w2 ^4 E  W
    }7 s/ K2 T7 a6 Q. a
    return (cap -1) & result;7 x, C; z( i! X7 ?
    }
    5 J# u  z  j4 Q4 `9 x}
    ! x9 |: y) Q2 _4 j8 D}
    ' \* j8 M1 V  I3 D6 C2 `
    ) h4 A) i/ n, N+ \2 H# p9 P
    [url=][/url]
    7 y6 Y, N+ p! \" z" K; o' ~* q, }% X6 [+ ?' a) C+ o

    1 ~  m- b2 D9 Q1 W2 q) z
    - B8 Y( Z1 h7 d) K% }" F
    ) |& O+ K" U% f6 W
    参考文献:
    / s# c* V7 e1 C( n
    [1]Pei Cao. Bloom Filters - the math.
    http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html
    [2]Wikipedia. Bloom filter.
    / p: A5 k8 ]: V( J2 B/ j$ i5 w
    3 M# Y7 q& s. Q3 K/ _& C

    : a- D/ _% i2 A, E) y& ?3 C3 P- D0 e6 @* M0 ~+ R2 g
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-4-12 12:19 , Processed in 0.418655 second(s), 51 queries .

    回顶部