QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1512|回复: 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 }  ?, D7 V8 G1 _# T3 h3 ~
    BloomFilter——大规模数据处理利器
      Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法。通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合。

    9 p' A2 O% H* w' x4 V& m* s
    . 实例
      为了说明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的一个映射位。
    . r. l5 E' c; s+ u7 P
      以上方法在数据量较小的情况下都能完美解决问题,但是当数据量变得非常庞大时问题就来了。
      方法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实际上没有没网络蜘蛛访问,而将它们错判为已访问的代价是很小的——大不了少抓几个网页呗。
    % r/ n, f, _, k- v
    . 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 。
    3 W( V, `& u7 m, S: c$ f' Z6 b
    (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个二进制位了。

    # E' _5 q4 m+ e- f
    (2) 检查字符串是否存在的过程

      V7 B' u$ v8 S2 `
      下面是检查字符串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存在。
    ' Z& r# }. n! m" M
      若一个字符串对应的Bit不全为1,则可以肯定该字符串一定没有被Bloom Filter记录过。(这是显然的,因为字符串被记录过,其对应的二进制位肯定全部被设为1了)
      但是若一个字符串对应的Bit全为1,实际上是不能100%的肯定该字符串被Bloom Filter记录过的。(因为有可能该字符串的所有位都刚好是被其他字符串所对应)这种将该字符串划分错的情况,称为false positive 。

    - z1 u2 P6 L% N1 Q' a" O9 X
    (3) 删除字符串过程
       字符串加入了就被不能删除了,因为删除会影响到其他字符串。实在需要删除字符串的可以使用Counting bloomfilter(CBF),这是一种基本Bloom Filter的变体,CBF将基本Bloom Filter每一个Bit改为一个计数器,这样就可以实现删除字符串的功能了。

    ( N. U/ r" Z, L6 B( h% s
      Bloom Filter跟单哈希函数Bit-Map不同之处在于:Bloom Filter使用了k个哈希函数,每个字符串跟k个bit对应。从而降低了冲突的概率。

      }  \$ t7 q( M( ?' o
    . Bloom Filter参数选择
    ( R0 z: |; H5 l( c: u
       (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 ,这个概率基本能满足网络爬虫的需求了。  

    0 A- N  A: V. }8 `; b
    . Bloom Filter实现代码
        下面给出一个简单的Bloom Filter的Java实现代码:
    % e* U# s( d4 S) O! b2 s3 {
    [url=][/url]
    2 e) F6 b9 J, H, _# Ximport java.util.BitSet;+ f, A" ?, z0 j
    % J, n+ u# O6 ?( g% S7 O
    publicclass BloomFilter 0 _) w& _+ s0 h& Z- B
    {
    0 V9 x$ W$ }. y, F/* BitSet初始分配2^24个bit */ * j5 X$ S( }7 U
    privatestaticfinalint DEFAULT_SIZE =1<<25;
    : k2 D% n8 n0 }+ p, @) k# E5 o/* 不同哈希函数的种子,一般应取质数 */
    $ t- `4 F  {% Kprivatestaticfinalint[] seeds =newint[] { 5, 7, 11, 13, 31, 37, 61 };
    5 z$ n- |1 c$ r6 `& u  T1 t) \9 |5 Aprivate BitSet bits =new BitSet(DEFAULT_SIZE);
    4 ?) o$ R' m0 T: Z/* 哈希函数对象 */ ' s; Y2 l4 X# L' m# m/ e% I2 e
    private SimpleHash[] func =new SimpleHash[seeds.length];
    2 Z# i* |! @0 I) b% q& d5 I- ]7 _! o7 T- a% f# |
    public BloomFilter() ( }# ?0 L, T) }4 q
    {7 X8 \# N* X& B
    for (int i =0; i < seeds.length; i++)* B1 Q+ G. S& c4 U& e; o
    {# |, ?5 r& q! |
    func =new SimpleHash(DEFAULT_SIZE, seeds);
    7 a$ |7 R% `9 I+ b8 L1 X}& o3 F! U( f3 V, b- f/ Q. h' p
    }8 j8 |3 [' w# d# W' B6 B

    ! O+ v3 u- M" S. U// 将字符串标记到bits中
    / }) f( D1 t- q  ]' B, p" K/ ]1 Upublicvoid add(String value)
    / m* p) W8 v- x{1 a2 n& c, @3 {/ `( X; |
    for (SimpleHash f : func)
      A2 J, R# K# [{
    ' |) _! e- X1 R+ x; O; ]/ Q' N; B% S  Fbits.set(f.hash(value), true);) C) ]1 [& S/ F% x+ I
    }6 ]% I) ~1 H& t# \$ z
    }! n4 G5 g7 D  [# {+ [3 l, w8 }" Z
    ; c' Z4 S0 q( x) s5 u
    //判断字符串是否已经被bits标记- z1 o7 b" Z  ?' @
    publicboolean contains(String value)
    ) i* `4 n: x3 t  @- J2 M{
    0 M  X1 G4 K& sif (value ==null)
    9 z; s! k/ q1 v! y: g4 i7 R{
    $ k0 ~8 K% s/ O& C5 x5 sreturnfalse;
    7 b: p. U$ [7 |- m4 I}2 n+ i% q9 P/ r( P3 x& S
    boolean ret =true;) }* Q/ j' L; F2 L5 [( m
    for (SimpleHash f : func)
    5 j4 L) L. B. q2 z8 H& ^{
    ( X! N& u1 C# |, \8 N0 Yret = ret && bits.get(f.hash(value));
    5 P# q1 {& K) G: @: z}
    " q$ j5 y+ Q3 ?1 E' [2 J. Kreturn ret;
    ( ^6 B, y3 |3 r$ E1 a}
    % E. M/ C  u* D/ v
    6 l, N% T0 s. [* o$ s9 k/* 哈希函数类 */
    , z  Q) L; U, gpublicstaticclass SimpleHash
    2 {3 _% v9 S# ?. F7 X{) L) D0 N! \/ }
    privateint cap;% |4 D" o, ~* x, R. p* }) R4 m
    privateint seed;
    ( _) a( }) }0 U6 |3 G% H
    ! \3 W, g3 j. u8 I! r  z, s2 fpublic SimpleHash(int cap, int seed)
    : h2 d3 t1 s: X3 H' R3 C% j, Z% z{& k& l/ t' e( ^3 c/ J+ o
    this.cap = cap;8 Y6 ^1 G9 v! l3 n
    this.seed = seed;
    , I* G# v9 B' j4 `$ z}
    1 o) s" b, |& Z8 T1 C; @* d$ T" q6 b2 y
    //hash函数,采用简单的加权和hash6 t; i$ N* m  M3 h% h- y
    publicint hash(String value) / g6 t' B2 ]- `* u& l, J. a
    {
    8 B% k! `+ V1 pint result =0;7 U1 p5 S2 b! D. d; ?! E+ y$ Z( Z
    int len = value.length();2 N% u0 U, V7 B8 y1 k
    for (int i =0; i < len; i++) ; u9 Y9 a) J( E
    {7 i" J  ~9 e- K+ k
    result = seed * result + value.charAt(i);
    ) U6 S6 B  d2 w5 X}
    " d" ^5 r: K7 o1 `+ [7 breturn (cap -1) & result;# N' i) e+ }- O- Z* d
    }
    ( j, V. U' Y" i- i% F5 x; E6 G# e}) D8 q1 {4 `" M! Z, ^
    }
    , ]! N2 x, b% \/ Z; n
    : x% v2 K; G: @8 b6 @% z5 P6 l8 w
    [url=][/url]2 }3 H7 G! G# t1 @$ g
    5 P. @8 \  _4 g

      j: q, X& g  p/ R) s, f, F- S# h* w! u4 A
    5 I' l! W8 ?# X$ W3 j0 u6 M$ i2 P
    参考文献:

    " ~: Q( z% d6 f9 d
    [1]Pei Cao. Bloom Filters - the math.
    http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html
    [2]Wikipedia. Bloom filter.
    $ e5 j1 _8 g4 w$ _8 N7 ]
    ! u8 l# [7 J5 e, X2 U7 l8 d  R: e) C
    + N  X. K* o- p) I; [% I& K
      \, T1 O7 i' M- x+ O% {# E  ^
    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-9-12 19:47 , Processed in 1.049103 second(s), 50 queries .

    回顶部