Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

BloomFilter布隆过滤器 简介

丈黑起恋秘
149 0 0
布隆过滤器
0 _/ Z( B4 q( l* B7 c' ], S布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。7 w0 [4 _0 @' ~7 r% g% U1 l
布隆过滤器 (Bloom Filter)是一种space efficient的概率型数据结构,在垃圾邮件过滤的黑白名单方法、爬虫(Crawler)的网址判重模块中等等经常被用到。哈希表也能用于判断元素是否在集合中,但是布隆过滤器只需要哈希表的1/8或1/4的空间复杂度就能完成同样的问题。布隆过滤器可以插入元素,但不可以删除已有元素。其中的元素越多,false positive rate(误报率)越大,但是false negative (漏报)是不可能的。4 V" Y. U( Z7 W; B; ^0 \
基本概念: a6 H5 p8 q/ O; ^
如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为 O(n),O(log n),O(n/k)。+ k& T% l# D5 d
布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。" s; ^$ ]0 R& e0 @
算法描述/ n4 W) z% R  ~8 o9 E/ l5 r, U
一个empty bloom filter是一个有m bits的bit array,每一个bit位都初始化为0。并且定义有k个不同的hash function,每个都以uniform random distribution将元素hash到m个不同位置中的一个。在下面的介绍中n为元素数,m为布隆过滤器或哈希表的slot数,k为布隆过滤器重hash function数。
# {3 v1 y% O0 y, b- d为了add一个元素,用k个hash function将它hash得到bloom filter中k个bit位,将这k个bit位置1。
( Y5 O# `3 g' \* U5 g为了query一个元素,即判断它是否在集合中,用k个hash function将它hash得到k个bit位。若这k bits全为1,则此元素在集合中;若其中任一位不为1,则此元素比不在集合中(因为如果在,则在add时已经把对应的k个bits位置为1)。0 y( i2 k. Q1 L; @  F% U8 k. Z' ~
不允许remove元素,因为那样的话会把相应的k个bits位置为0,而其中很有可能有其他元素对应的位。因此remove会引入false negative,这是绝对不被允许的。5 Z6 K% v  F3 P, y4 ?
当k很大时,设计k个独立的hash function是不现实并且困难的。对于一个输出范围很大的hash function(例如MD5产生的128 bits数),如果不同bit位的相关性很小,则可把此输出分割为k份。或者可将k个不同的初始值(例如0,1,2, … ,k-1)结合元素,feed给一个hash function从而产生k个不同的数。
5 ]9 q, l$ ?# M) X  {" B6 ?当add的元素过多时,即n/m过大时(n是元素数,m是bloom filter的bits数),会导致false positive过高,此时就需要重新组建filter,但这种情况相对少见。6 Q+ ^' Z) ?& B% x- Q3 J) k5 T2 [
优点
9 V7 S# E: D4 q; C- b) L相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数(O(k))。另外,散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。+ d$ k2 }$ F- g4 `8 z, b0 j
布隆过滤器可以表示全集,其它任何数据结构都不能;( s7 ?' x3 ^4 C0 a' t  G
缺点. T* D5 B- P! Z7 s/ d
但是布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。
, n" A& T3 w  y  L+ f1 r另外,一般情况下不能从布隆过滤器中删除元素。我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面。这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。7 ?0 n* \9 ^& O' n- F, n1 @8 g
在降低误算率方面,有不少工作,使得出现了很多布隆过滤器的变种。9 V9 K  p# E+ h. E6 z
举例说明布隆过滤器的空间优势
2 H: a2 X( P9 Z; J先来一个结论:对于一个有1%误报率和一个最优k值的布隆过滤器来说,无论元素的类型及大小,每个元素只需要9.6 bits来存储。这个优点一部分继承自array的紧凑性,一部分来源于它的概率性。如果你认为1%的误报率太高,那么对每个元素每增加4.8 bits,我们就可将误报率降低为原来的1/10。add和query的时间复杂度都为O(k),与集合中元素的多少无关,这是其他数据结构都不能完成的。k是hash函数的个数。
  j8 g) @" z! _* d0 E8 f! P  W举例: 现有1亿个email的黑名单,元素的数量(即email列表)为 108。若采用布隆过滤器,取k=8(k为hash函数个数)。因为n为1亿,所以总共需要8*108。又因为在保证误判率低(后面解释)且k和m选取合适时,空间利用率为50%(后面会解释),所以总空间为5 M4 w; E/ |9 |

7 R$ `1 m" t5 Q, W空间优势) w) `" G  O+ K5 P- l" V: z
所需空间比上述哈希结构或者数组小得多,并且误判率在万分之一以下。为什么可以这样算,可以看下面。4 G: P/ N( o& w  _0 \. y/ D! ]
误判概率的证明和计算
9 N, X2 x( o( A! F8 X$ p该过程的详细说明来自于这个文章http://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html,为了看懂求导过程,需要复习数学知识。
$ I  C0 }. O4 N  Y3 J5 f& ~3 N对某一特定bit位在一个元素由某特定hash function插入时没有被置位为1的概率为:" t, |5 S4 ]5 H$ Q

# |7 A, p6 Q( `! i, H6 y0 B则k个hash function中没有一个对其置位的概率为:
+ h" |' H# ~, r; D/ l, d- d& U" e: F8 J- L9 M: @* ?
如果插入了n个元素,但都未将其置位的概率为:% I* C, ^/ s* F9 w5 t8 |1 t  j

1 S2 ]& ^4 j  I4 L! p6 T现在考虑query阶段,若对应某个待query元素的k bits全部置位为1,则可判定其在集合中。因此将某元素误判的概率为:3 ~/ q9 g# E4 H9 B( o5 V% F
6 Z) Y! J% w/ z# C1 T
由于( S: A6 p3 ]$ v  p3 f" j
' u( a/ D! U8 u% V3 ]
,并且1/m 当m很大时趋近于0,所以
' k7 |0 C/ w1 t% d现在计算对于给定的m和n,k为何值时可以使得误判率最低。设误判率为k的函数为:
2 V% B" e% t, l8 N5 @( ~  c3 ]
! r5 x  q' l( J
/ X7 S- l! w; D2 p2 U% f" ^# e% B9 P; ]* g6 R; S$ v
则简化为0 I4 h. B0 B  Y3 e  U( w
- l9 b* g% W& |* z# l
因为等式右边的底数上是函数,指数上也是函数,没有方法求这样组合函数的导数,只能取对数之后,变成乘法。我们有两个函数相乘的求导方法,求导的几个方法可以看参考资料,有很好的视频说明。
* V* l( U8 b; _. I: P, `" a$ {两边取对数得' L  [% {; j9 f
7 R! W+ p# K9 I; b) c3 H
两边对k求导得,这边涉及到乘法求导,对数求导,幂函数求导:9 F4 W1 {$ C/ t) o
% L' ~; i. f; G1 E+ G7 L1 }
下面求最值,
. ]2 v& K% R# g1 N" k9 u" \7 `) Z
红圈中的等式是把两边看成xln(x)这种形式得到的,和该函数的单调性相关。数学上能不能这么操作我还不太清楚。数学好的大神可以留言解释一下。9 E! Z) p7 T* E' ^1 i
因此,即当& [" n! m- W6 R( D/ Y3 q" n3 K7 t

# H7 Y* S& G  ?时误判率最低,此时误判率为:4 v5 V! |; H2 S3 E
2 M$ N2 e$ X% h
可以看出若要使得误判率≤1/2,则:
* `/ i. _6 W! V- s* j# x; A7 t) C; L+ j5 L
这说明了若想保持某固定误判率不变,布隆过滤器的bit数m与被add的元素数n应该是线性同步增加的。; j# ~7 y) M7 u$ b7 P" @
设计和应用布隆过滤器的方法
4 x) J, ~( C2 |$ t! l2 K应用时首先要先由用户决定要add的元素数n和希望的误差率P。这也是一个设计完整的布隆过滤器需要用户输入的仅有的两个参数,之后的所有参数将由系统计算,并由此建立布隆过滤器。8 ^: X: }2 F# a+ `: _* y" p
系统首先要计算需要的内存大小m bits:
0 U- C! N& e8 ?& t$ }! Z6 M% b6 E2 b/ N7 |) D, `5 Z
再由m,n得到hash function的个数:
/ V, u/ a5 U9 F9 r( W9 ?) v) R  ^% g& V: f8 z( p3 U! V
至此系统所需的参数已经备齐,接下来add n个元素至布隆过滤器中,再进行query。# ~/ P. _$ w  R: Z& V2 q9 v& }
根据公式,当k最优时:. z; ?2 K" d/ j1 f6 }# `
% F4 L! u3 m9 R8 v% W9 N& A2 a
因此可验证当P=1%时,存储每个元素需要9.6 bits:
8 N6 Q2 h& Y0 h8 t' n' q3 M; R. P# l4 K% O. ?5 z4 f
而每当想将误判率降低为原来的1/10,则存储每个元素需要增加4.8 bits:
7 d2 j& H9 N/ ]; P6 P; |$ w  j" f) h1 G8 G6 q$ S
这里需要特别注意的是,9.6 bits/element不仅包含了被置为1的k位,还把包含了没有被置为1的一些位数。此时的
! n0 b2 O9 U+ R; @7 h9 j( _! K: F$ u' C+ o3 ~7 d) b
才是每个元素对应的为1的bit位数。9 N5 d& m( O5 @" h6 u( l

6 h$ b; U' h, C# h从而使得P(error)最小时,我们注意到:
2 j, K# B, i2 Y4 j8 ]- o
. M( c4 m. l* y7 j中的6 \1 f: N( F3 _6 n5 Y3 a& z1 I
,即
- C) }6 B" T: L8 F+ A7 |7 B: R
. U% B) ?5 B. ?  W; v此概率为某bit位在插入n个元素后未被置位的概率。因此,想保持错误率低,布隆过滤器的空间使用率需为50%。
( Z; I: U! h$ W. l8 e" |; TNeo中的布隆过滤器' b) A6 [) [8 n7 G5 k0 c$ g7 m
上面的内容大部分抄袭http://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html,原作者写的太好了,我只是加上一些我的理解,方便数学不好的道友理解。下面我们看看Neo中的Bloom Filter。2 X# t5 f  i: y2 ^7 [& ~: v( B
using System.Collections;+ f+ u+ @/ y7 v" U' z
using System.Linq;6 r' e/ E6 P/ T9 b! q
namespace Neo.Cryptography
' z; ?6 A! P# R' M5 J{3 N7 M! c0 f' X+ u7 a
    public class BloomFilter
# S$ W# B: W. ?. z, ^5 e    {
$ ]6 [, v& H- X/ o! T% B6 d        private readonly uint[] seeds;
- E; }0 i6 [$ M/ C  ?        private readonly BitArray bits;0 c4 ^" t8 v/ V: `" ~) G' ]9 M
        public int K => seeds.Length;; t) K6 c' o0 {" s$ e+ ]
        public int M => bits.Length;
+ @2 e2 D' t6 T/ v6 Y2 J        public uint Tweak { get; private set; }5 d9 l$ k0 }2 C% n0 D3 g! H
        public BloomFilter(int m, int k, uint nTweak, byte[] elements = null)
/ i" H9 `5 x1 z" x9 d, T        {( e! ]2 R" d7 d+ Z" `/ g1 Z
            this.seeds = Enumerable.Range(0, k).Select(p => (uint)p * 0xFBA4C795 + nTweak).ToArray();+ m5 i) ]; Y: l4 P) b# f* R
            this.bits = elements == null ? new BitArray(m) : new BitArray(elements);% t9 d4 _  |, l# Y2 j1 N: ?8 ^6 o
            this.bits.Length = m;6 Y2 ^8 V' ~* e! V
            this.Tweak = nTweak;; l$ b8 ~. z6 B' K: f
        }
0 s. E% V. Q' p; ~        public void Add(byte[] element)6 ~# @6 i4 c! v) ?
        {
8 c. t  Z8 y' v" F0 @            foreach (uint i in seeds.AsParallel().Select(s => element.Murmur32(s)))
) o$ a- ~3 }0 |* V7 ^  \                bits.Set((int)(i % (uint)bits.Length), true);
, f" ?# K' `8 J" r        }
  }( j: _) W1 [) f8 y        public bool Check(byte[] element)& {* I* i# |$ o. S
        {. Y1 |- |  F! Q) r7 P* D
            foreach (uint i in seeds.AsParallel().Select(s => element.Murmur32(s)))
  S$ {( d1 C- H, s# C                if (!bits.Get((int)(i % (uint)bits.Length)))# M$ {. X  x" J
                    return false;
2 ^6 ]! I/ b! f" }            return true;( \' Q# A  w! ]3 A6 D
        }
/ _2 i( U& l$ d7 b, E        public void GetBits(byte[] newBits)' E. B; X0 F; n) ^% c
        {) k" r% G0 ]3 |" l- C
            bits.CopyTo(newBits, 0);
' P5 j; V' G2 N) o        }
. x  v+ n' j+ q; c    }
8 e. P# y& K. N9 [7 L}
% b% o  ^+ N+ j; p前面讲了这么多,代码竟然这么短,分析分析。
3 W4 J+ r% _$ G5 S构造函数传入了m(多少位),k(hash函数种类),这个和我们前面分析根据p(错误率),和n(要插入的元素)来构造的思路不一样。所以Neo的这个版本应该是一个简化版本,输入的数据n应该是有范围的,具体的范围我们后面运行整个区块链的时候在观察,现在不知道n的个数有多大。hash函数使用了Murmur32,然后传入不同的seed模拟不同的hash函数,这个是可以的。使用linq,函数式编程代码非常简洁,这也是C#的一个优势啊。add,check函数都很容易看懂,确实实现很简洁。9 d: H- k3 F4 y  L% l
. f9 T* ~) Y4 U/ a7 z
总结8 F# M# T( b0 G6 @0 P. V" R
Bloom Filter是牛逼的数据结构,因为有很多数学知识在里面,虽然代码不长,但是能看完这篇文章的人,会感受到代码之美。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

丈黑起恋秘 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    2