Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

BloomFilter布隆过滤器 简介

丈黑起恋秘
85 0 0
布隆过滤器
  h3 r( y0 L0 v7 V, O5 ?' d3 |布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。0 a" {6 f8 T' q2 [6 q0 d; u1 {
布隆过滤器 (Bloom Filter)是一种space efficient的概率型数据结构,在垃圾邮件过滤的黑白名单方法、爬虫(Crawler)的网址判重模块中等等经常被用到。哈希表也能用于判断元素是否在集合中,但是布隆过滤器只需要哈希表的1/8或1/4的空间复杂度就能完成同样的问题。布隆过滤器可以插入元素,但不可以删除已有元素。其中的元素越多,false positive rate(误报率)越大,但是false negative (漏报)是不可能的。
7 U7 I; q$ R( S7 \, ~5 q" Q基本概念
* J: U0 W( ]1 e' v! A- E如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为 O(n),O(log n),O(n/k)。$ \6 d5 K1 I/ V1 @
布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。& v/ F3 R  x# @* g  P, A' q. t
算法描述; ]( W5 R! R8 I5 }
一个empty bloom filter是一个有m bits的bit array,每一个bit位都初始化为0。并且定义有k个不同的hash function,每个都以uniform random distribution将元素hash到m个不同位置中的一个。在下面的介绍中n为元素数,m为布隆过滤器或哈希表的slot数,k为布隆过滤器重hash function数。
6 l% J) Q. d& d0 X为了add一个元素,用k个hash function将它hash得到bloom filter中k个bit位,将这k个bit位置1。
0 |8 Y, t7 d' g7 N为了query一个元素,即判断它是否在集合中,用k个hash function将它hash得到k个bit位。若这k bits全为1,则此元素在集合中;若其中任一位不为1,则此元素比不在集合中(因为如果在,则在add时已经把对应的k个bits位置为1)。9 g" d5 l; ^4 t7 M& }+ V
不允许remove元素,因为那样的话会把相应的k个bits位置为0,而其中很有可能有其他元素对应的位。因此remove会引入false negative,这是绝对不被允许的。% O! M' u6 W8 ~5 n2 j& O/ ]
当k很大时,设计k个独立的hash function是不现实并且困难的。对于一个输出范围很大的hash function(例如MD5产生的128 bits数),如果不同bit位的相关性很小,则可把此输出分割为k份。或者可将k个不同的初始值(例如0,1,2, … ,k-1)结合元素,feed给一个hash function从而产生k个不同的数。
. N( T  e/ S4 ]  Q% [% E当add的元素过多时,即n/m过大时(n是元素数,m是bloom filter的bits数),会导致false positive过高,此时就需要重新组建filter,但这种情况相对少见。
' M" e3 W) \! h, X, Y7 s优点
; e6 R1 y, C6 x9 q! C; F+ j. l: T相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数(O(k))。另外,散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。
, U! s2 }7 L* \( q4 W4 a; o8 Y布隆过滤器可以表示全集,其它任何数据结构都不能;
) g  f4 |, ]' j" O缺点9 D! r. A4 F% N3 B, P
但是布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。! z' k2 K- y; @3 h. Q2 j2 w
另外,一般情况下不能从布隆过滤器中删除元素。我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面。这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。+ y, V% _  x  F* c: @8 T6 B
在降低误算率方面,有不少工作,使得出现了很多布隆过滤器的变种。. ^, ]; h* g5 b7 a6 {5 V
举例说明布隆过滤器的空间优势
+ _9 R; }# T" b- a* D先来一个结论:对于一个有1%误报率和一个最优k值的布隆过滤器来说,无论元素的类型及大小,每个元素只需要9.6 bits来存储。这个优点一部分继承自array的紧凑性,一部分来源于它的概率性。如果你认为1%的误报率太高,那么对每个元素每增加4.8 bits,我们就可将误报率降低为原来的1/10。add和query的时间复杂度都为O(k),与集合中元素的多少无关,这是其他数据结构都不能完成的。k是hash函数的个数。3 K/ f* _# b4 s+ ]: ^- s; p0 s& ~
举例: 现有1亿个email的黑名单,元素的数量(即email列表)为 108。若采用布隆过滤器,取k=8(k为hash函数个数)。因为n为1亿,所以总共需要8*108。又因为在保证误判率低(后面解释)且k和m选取合适时,空间利用率为50%(后面会解释),所以总空间为1 o4 X* {2 I. w4 U, `+ N7 w
# j3 q7 m& W6 k  [9 O7 A
空间优势
, B; i* V8 z# `& ]% y: g所需空间比上述哈希结构或者数组小得多,并且误判率在万分之一以下。为什么可以这样算,可以看下面。8 w- `5 k* S: G
误判概率的证明和计算
$ u* P# M! n/ \, d# l该过程的详细说明来自于这个文章http://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html,为了看懂求导过程,需要复习数学知识。
. d. `, F7 ]$ V# P8 K) T对某一特定bit位在一个元素由某特定hash function插入时没有被置位为1的概率为:( _% {; Q+ B0 k& z0 v
; V5 G3 z' g; B6 |( j
则k个hash function中没有一个对其置位的概率为:
! b3 T5 l% u+ S1 }4 h0 t# M' ~1 D
如果插入了n个元素,但都未将其置位的概率为:, B  m" L; v$ @. Z
3 E8 s* D, a$ W  q- K$ @5 c
现在考虑query阶段,若对应某个待query元素的k bits全部置位为1,则可判定其在集合中。因此将某元素误判的概率为:. B' }1 t# T/ B1 Y% C4 \
: E& ?6 S* P, a* R4 v, j3 U  E) i( X
由于$ w( }* ~( v- t3 i6 O8 j$ }

& Y! @9 }$ c  x+ W1 b,并且1/m 当m很大时趋近于0,所以
2 o7 ^$ |3 x9 d- [( v6 O! y  e, S现在计算对于给定的m和n,k为何值时可以使得误判率最低。设误判率为k的函数为:/ f  [8 D5 F- \! |' g1 O' g! d* a
1 \* s' e' P% Z- l0 r, M8 R
  y6 F! Q# e$ `1 Y

8 X2 y, h$ i- i( E$ G* g则简化为
1 x9 u* W9 f1 ~$ w3 a# j" X+ S
1 q* G- o/ U& Q8 ^因为等式右边的底数上是函数,指数上也是函数,没有方法求这样组合函数的导数,只能取对数之后,变成乘法。我们有两个函数相乘的求导方法,求导的几个方法可以看参考资料,有很好的视频说明。. @3 P3 D5 B; |5 ^7 A
两边取对数得
, m! e' w; x. O. n' I" q9 \5 C1 }- E$ R! T  l/ D4 n* Z
两边对k求导得,这边涉及到乘法求导,对数求导,幂函数求导:
. A: |$ }6 L3 a1 e! h/ G- F
+ U$ O' o  i: o下面求最值,
9 I: ]/ J- \$ T, y- `' R! ~( N4 b9 r7 w- c% E% J
红圈中的等式是把两边看成xln(x)这种形式得到的,和该函数的单调性相关。数学上能不能这么操作我还不太清楚。数学好的大神可以留言解释一下。. Z( k' s4 F6 R$ k  c3 R. @
因此,即当, Y8 \2 h( T7 a  v
7 Q9 F6 o7 @: Z
时误判率最低,此时误判率为:
7 ?) w" @/ s) I0 ~3 x& b4 H$ y9 k& b5 z- e8 L; ^* E; Q
可以看出若要使得误判率≤1/2,则:
' n# S, |  H. l* S# T$ y- |: p0 ]
这说明了若想保持某固定误判率不变,布隆过滤器的bit数m与被add的元素数n应该是线性同步增加的。
% f) Y2 ~  D7 \设计和应用布隆过滤器的方法1 {7 d( l# H" t' O0 }" `
应用时首先要先由用户决定要add的元素数n和希望的误差率P。这也是一个设计完整的布隆过滤器需要用户输入的仅有的两个参数,之后的所有参数将由系统计算,并由此建立布隆过滤器。/ g: o( b/ d, S! R6 _& \, D
系统首先要计算需要的内存大小m bits:4 X/ [* p/ S0 x! h# Q  s

- C5 J& S+ \5 A% J7 z; j& l( d再由m,n得到hash function的个数:
9 Q9 l, z, B7 s! O# J, o) ^6 H/ x# r9 a# v: {( \
至此系统所需的参数已经备齐,接下来add n个元素至布隆过滤器中,再进行query。9 ]1 L% D+ E" ~
根据公式,当k最优时:
" s( j: _! z- b' r$ q
  x4 }3 B7 I" B4 N' |1 r因此可验证当P=1%时,存储每个元素需要9.6 bits:5 T# K! O8 K/ C6 n7 L' E% |) }
7 j% p( b2 v# Z  e$ n" F: a
而每当想将误判率降低为原来的1/10,则存储每个元素需要增加4.8 bits:+ h6 R9 U1 }8 v2 k9 D2 q% b8 k3 i

! @6 w1 N0 j& b, J5 o这里需要特别注意的是,9.6 bits/element不仅包含了被置为1的k位,还把包含了没有被置为1的一些位数。此时的
$ J& D7 j; W2 y
! k1 L. R9 g  X9 T; h才是每个元素对应的为1的bit位数。
; I1 m% p3 H4 H3 d' D
0 q- f$ ~* f4 r5 j/ z2 d从而使得P(error)最小时,我们注意到:4 u( U- T$ g/ S# W% V9 F
& u, V( N( e/ J2 F' l
中的9 n8 e/ l4 Y1 {1 z7 ?
,即
9 T- b, T, F2 _9 w
, z( ]+ F1 e5 b2 @( e# [( n/ m此概率为某bit位在插入n个元素后未被置位的概率。因此,想保持错误率低,布隆过滤器的空间使用率需为50%。
* t+ B, Q8 s+ p( L+ VNeo中的布隆过滤器
3 x. e" j6 f/ a6 j. P上面的内容大部分抄袭http://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html,原作者写的太好了,我只是加上一些我的理解,方便数学不好的道友理解。下面我们看看Neo中的Bloom Filter。
1 L3 I5 O* Z$ w( L4 K! O, musing System.Collections;) L( m8 Z; D& k( l: s; ?1 k( W
using System.Linq;
3 p; \6 T# C  `& S- \namespace Neo.Cryptography
5 s/ {/ }7 e2 Y, ]{+ U4 ?& e3 \7 g& N: C+ X5 _: p, A
    public class BloomFilter
/ [9 g4 ~0 [8 R. S+ D    {1 p; h6 s- f# z
        private readonly uint[] seeds;9 L; i5 M  y4 ?. L& ]% K. w
        private readonly BitArray bits;
: c2 J; k& A/ w" a- w5 n        public int K => seeds.Length;
. r* J  j4 {: g. j  X9 g        public int M => bits.Length;
0 x4 E( t, E# h* p        public uint Tweak { get; private set; }/ @' N' f5 {! C* e# r$ c. o
        public BloomFilter(int m, int k, uint nTweak, byte[] elements = null)
% L& Y& {2 g! r' W: u) f" W4 B3 j        {& S4 S2 o; o& [4 q
            this.seeds = Enumerable.Range(0, k).Select(p => (uint)p * 0xFBA4C795 + nTweak).ToArray();: ~; P4 L: t, Y, C& f
            this.bits = elements == null ? new BitArray(m) : new BitArray(elements);
% v4 D( |8 q, E  l+ B            this.bits.Length = m;
4 S3 e$ H4 `  y% A0 H) I            this.Tweak = nTweak;
" o' F7 F  ]) R        }; z9 }5 H' [3 ?" s9 l+ v. x
        public void Add(byte[] element)) y+ a* h1 P( l, @* R: T. i" z
        {
4 f; l, `1 j. t' b            foreach (uint i in seeds.AsParallel().Select(s => element.Murmur32(s)))
. |% A; P2 |% S+ r+ t                bits.Set((int)(i % (uint)bits.Length), true);
3 e/ [; t( G* n4 I1 \$ [5 R        }( K! X0 B- y# n" r
        public bool Check(byte[] element)" ?. |! |; ^7 Z) U/ S
        {  o+ }# i- w+ l  ~% r
            foreach (uint i in seeds.AsParallel().Select(s => element.Murmur32(s)))
- g# E' [8 }- j! D5 m9 Z                if (!bits.Get((int)(i % (uint)bits.Length)))
- i; `. B& [% _( V! R                    return false;# M% Z2 `. V  g( v
            return true;
) Z$ _/ C* K3 W' a& l        }9 Z1 J6 _5 @- c/ i
        public void GetBits(byte[] newBits)+ p$ Q! e/ L% {: f: k# U
        {
1 h0 d/ P, S  U! S9 ?5 Y* V            bits.CopyTo(newBits, 0);4 L$ M9 G- l* `; m4 g
        }
) C& E: h7 M2 h- K    }
, H; j8 H2 {* J. p$ H}
8 s$ n% ]+ z1 o; Q- p$ u前面讲了这么多,代码竟然这么短,分析分析。1 k1 L: Z) ]% u2 ^& K
构造函数传入了m(多少位),k(hash函数种类),这个和我们前面分析根据p(错误率),和n(要插入的元素)来构造的思路不一样。所以Neo的这个版本应该是一个简化版本,输入的数据n应该是有范围的,具体的范围我们后面运行整个区块链的时候在观察,现在不知道n的个数有多大。hash函数使用了Murmur32,然后传入不同的seed模拟不同的hash函数,这个是可以的。使用linq,函数式编程代码非常简洁,这也是C#的一个优势啊。add,check函数都很容易看懂,确实实现很简洁。; Z" h; {8 b6 V# |% y) }0 q
% p' m/ P+ U! i+ i. k
总结( q5 P: \$ ^; G5 N0 x4 U
Bloom Filter是牛逼的数据结构,因为有很多数学知识在里面,虽然代码不长,但是能看完这篇文章的人,会感受到代码之美。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

丈黑起恋秘 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    2