Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

BloomFilter布隆过滤器 简介

丈黑起恋秘
154 0 0
布隆过滤器
$ b3 [/ U: M; }& Y布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
+ G9 }4 V2 p" \布隆过滤器 (Bloom Filter)是一种space efficient的概率型数据结构,在垃圾邮件过滤的黑白名单方法、爬虫(Crawler)的网址判重模块中等等经常被用到。哈希表也能用于判断元素是否在集合中,但是布隆过滤器只需要哈希表的1/8或1/4的空间复杂度就能完成同样的问题。布隆过滤器可以插入元素,但不可以删除已有元素。其中的元素越多,false positive rate(误报率)越大,但是false negative (漏报)是不可能的。5 r# A$ Z7 `, m% C+ R8 h' l8 v8 b$ H
基本概念
' l: A! f2 j( n% Y4 K' H9 m' T如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为 O(n),O(log n),O(n/k)。
6 X# j7 i! y/ r& ~# Y布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。/ e- k2 t+ u4 ]$ }1 g1 \4 O
算法描述+ O6 S; N, h5 o, Y! o8 O2 E: D4 E, G1 w
一个empty bloom filter是一个有m bits的bit array,每一个bit位都初始化为0。并且定义有k个不同的hash function,每个都以uniform random distribution将元素hash到m个不同位置中的一个。在下面的介绍中n为元素数,m为布隆过滤器或哈希表的slot数,k为布隆过滤器重hash function数。9 e. _  H; R  c  Y
为了add一个元素,用k个hash function将它hash得到bloom filter中k个bit位,将这k个bit位置1。0 d0 s5 n2 {' m; L3 z$ _, d
为了query一个元素,即判断它是否在集合中,用k个hash function将它hash得到k个bit位。若这k bits全为1,则此元素在集合中;若其中任一位不为1,则此元素比不在集合中(因为如果在,则在add时已经把对应的k个bits位置为1)。
% L% |* A' Z/ k不允许remove元素,因为那样的话会把相应的k个bits位置为0,而其中很有可能有其他元素对应的位。因此remove会引入false negative,这是绝对不被允许的。. c/ L$ q) a5 R! s1 k9 {$ ]
当k很大时,设计k个独立的hash function是不现实并且困难的。对于一个输出范围很大的hash function(例如MD5产生的128 bits数),如果不同bit位的相关性很小,则可把此输出分割为k份。或者可将k个不同的初始值(例如0,1,2, … ,k-1)结合元素,feed给一个hash function从而产生k个不同的数。
) r1 H# o' e8 M- b$ M& u! g5 e当add的元素过多时,即n/m过大时(n是元素数,m是bloom filter的bits数),会导致false positive过高,此时就需要重新组建filter,但这种情况相对少见。
% j4 B$ ~; s* k% G! z, @优点2 k  b+ k* D1 a- S. u- K! w
相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数(O(k))。另外,散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。! L6 p8 ^8 U& X, v
布隆过滤器可以表示全集,其它任何数据结构都不能;/ e( f- |+ ]. [! l4 e7 x2 r
缺点
+ |+ \/ n! h. m7 J4 J4 B) |但是布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。
7 z, _' R. |# U9 x) H另外,一般情况下不能从布隆过滤器中删除元素。我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面。这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。8 m7 [7 A& n. E. k7 E/ W9 t
在降低误算率方面,有不少工作,使得出现了很多布隆过滤器的变种。
, g; S$ k+ n2 t4 H举例说明布隆过滤器的空间优势
6 S- D8 ~  o( i$ Y/ Y先来一个结论:对于一个有1%误报率和一个最优k值的布隆过滤器来说,无论元素的类型及大小,每个元素只需要9.6 bits来存储。这个优点一部分继承自array的紧凑性,一部分来源于它的概率性。如果你认为1%的误报率太高,那么对每个元素每增加4.8 bits,我们就可将误报率降低为原来的1/10。add和query的时间复杂度都为O(k),与集合中元素的多少无关,这是其他数据结构都不能完成的。k是hash函数的个数。
6 \# U+ @6 ]  @+ |% H举例: 现有1亿个email的黑名单,元素的数量(即email列表)为 108。若采用布隆过滤器,取k=8(k为hash函数个数)。因为n为1亿,所以总共需要8*108。又因为在保证误判率低(后面解释)且k和m选取合适时,空间利用率为50%(后面会解释),所以总空间为) a4 z, l3 L8 `* ?& K5 {
: T7 `1 k* B4 `; L& B, \$ w
空间优势) y" X8 A2 a+ g4 T5 ^
所需空间比上述哈希结构或者数组小得多,并且误判率在万分之一以下。为什么可以这样算,可以看下面。+ J7 c* d0 Q) B- L
误判概率的证明和计算# B. x- L' h" a
该过程的详细说明来自于这个文章http://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html,为了看懂求导过程,需要复习数学知识。
: [0 O! ^3 A; N5 r3 T# P2 [对某一特定bit位在一个元素由某特定hash function插入时没有被置位为1的概率为:
$ U1 ^# O  W- ]# I4 |9 {
, `9 z# M( [$ r+ [6 ]则k个hash function中没有一个对其置位的概率为:
: F0 e* ^0 n5 A8 ?5 {5 R$ o
( z! W1 I: G: L* l. z/ K1 @, y. G* d如果插入了n个元素,但都未将其置位的概率为:
; S' m# V" B- c  d; W
0 D9 X. }5 G; \" c& n0 D, e现在考虑query阶段,若对应某个待query元素的k bits全部置位为1,则可判定其在集合中。因此将某元素误判的概率为:
" M, f; C3 V8 Y0 m2 W/ ~
; R9 J% F- B+ ^& c由于, F9 i8 k/ ~3 Z8 O; \- p

+ P& w, Q# D& k# |' r0 x: E,并且1/m 当m很大时趋近于0,所以; ^0 [( u2 ?: r  t# b$ M. e# }+ M6 o
现在计算对于给定的m和n,k为何值时可以使得误判率最低。设误判率为k的函数为:) v* C: u! F1 @/ B, E" |

, A4 @+ _' {6 k# ?" z: @- v- B7 m* Z1 s' ?1 Z7 ~9 J; W

% E+ j- a' ^2 \0 d$ f, S5 A; o则简化为, M4 E8 v9 z& S

7 ]& C9 M7 P3 }) ]. T: a因为等式右边的底数上是函数,指数上也是函数,没有方法求这样组合函数的导数,只能取对数之后,变成乘法。我们有两个函数相乘的求导方法,求导的几个方法可以看参考资料,有很好的视频说明。% h" a( @( q5 G- o9 K
两边取对数得' z5 \) V) M% C6 u0 U8 Y" b4 p
) K$ i0 `) o$ F  C! s" _7 W0 T
两边对k求导得,这边涉及到乘法求导,对数求导,幂函数求导:
" y6 [5 q# u: L% c5 S, D! U& r; }) y8 D' D/ n) M+ Q' }
下面求最值,
5 k1 z" \' ^; K
! M; f9 _. P$ [8 H( {" d红圈中的等式是把两边看成xln(x)这种形式得到的,和该函数的单调性相关。数学上能不能这么操作我还不太清楚。数学好的大神可以留言解释一下。+ o6 V5 V0 D2 O1 E0 L# z/ i: h
因此,即当; H+ R* V8 @( Y  y! h" \9 H% F

/ K" b2 ^9 ~2 z( I  l# h6 V( J时误判率最低,此时误判率为:
, |- h+ t! r3 M/ K3 n* J# G1 \2 u: `" O- t6 D# `) h
可以看出若要使得误判率≤1/2,则:
2 m. t$ V$ q/ Y$ N' c: H, w
0 j1 Y& Z- ~- }6 o这说明了若想保持某固定误判率不变,布隆过滤器的bit数m与被add的元素数n应该是线性同步增加的。
9 d/ f; Y3 w' @1 J2 \- w% D; G设计和应用布隆过滤器的方法
/ Q0 A7 n% A/ m5 F- l1 U) V9 l应用时首先要先由用户决定要add的元素数n和希望的误差率P。这也是一个设计完整的布隆过滤器需要用户输入的仅有的两个参数,之后的所有参数将由系统计算,并由此建立布隆过滤器。8 G7 k+ E3 W9 s) ?/ x
系统首先要计算需要的内存大小m bits:6 v7 }, k$ [3 S- m' w( @- w5 d

. W; \: k, i- H" S再由m,n得到hash function的个数:# X, u! [. |( V  Q; N! d% u
: s+ Q5 q( C7 m4 N8 h
至此系统所需的参数已经备齐,接下来add n个元素至布隆过滤器中,再进行query。
+ B* ^& ]; a( `2 J- V& \; p根据公式,当k最优时:
3 t; o9 {& R; j9 r7 L0 i9 H5 |$ u, L: ?1 ]
因此可验证当P=1%时,存储每个元素需要9.6 bits:) z0 f: c. [  n% d# n7 i
0 z, u# Z/ b9 s, o) I
而每当想将误判率降低为原来的1/10,则存储每个元素需要增加4.8 bits:9 q' q# H* z0 G! }' N7 y
+ R( w5 p$ l" |, M; o6 e
这里需要特别注意的是,9.6 bits/element不仅包含了被置为1的k位,还把包含了没有被置为1的一些位数。此时的/ E# Y* t. N# L$ ~$ _0 f1 J
0 J6 j! I/ x" i- X; _. Z
才是每个元素对应的为1的bit位数。0 u; G( h! _! Q( S) c) Z" K& C4 k% @: F
  p3 \+ R! a$ }; v2 i
从而使得P(error)最小时,我们注意到:
7 o6 P/ [- W. V5 }1 F0 s6 R  k. H" ^2 I: v4 X9 w
中的
  u% f: ~7 \7 N; T" u( _4 n,即
( C' ]0 b& ^/ a* p0 a9 W4 x6 A& b( Y0 J; A9 N- s
此概率为某bit位在插入n个元素后未被置位的概率。因此,想保持错误率低,布隆过滤器的空间使用率需为50%。; z2 r% u* [: p
Neo中的布隆过滤器$ O8 e6 c5 @4 T6 ?" s: \
上面的内容大部分抄袭http://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html,原作者写的太好了,我只是加上一些我的理解,方便数学不好的道友理解。下面我们看看Neo中的Bloom Filter。, U5 S6 `& P& G, w; b: c
using System.Collections;
6 b3 ?. R; F1 J+ F3 vusing System.Linq;& c. \6 u* ?- L. N# A) S  b
namespace Neo.Cryptography
& A/ i  B" x) z. c/ |8 S. @{  z: N3 J, a) W8 r
    public class BloomFilter2 H. o' S0 T4 J
    {, {, |  {1 z" e! O. I" Z
        private readonly uint[] seeds;; ~. `# h9 N, O2 T: Q
        private readonly BitArray bits;
& {9 O, i' e4 x) H        public int K => seeds.Length;
. ?/ |" ^, i7 j6 _        public int M => bits.Length;
8 j1 E/ T* W& Y/ W, R. s& @        public uint Tweak { get; private set; }
8 e% C" `) O+ V        public BloomFilter(int m, int k, uint nTweak, byte[] elements = null)
2 a" j, v  G5 ^1 K" u# t' O        {% y" j$ S/ c7 {4 v7 X( L* C
            this.seeds = Enumerable.Range(0, k).Select(p => (uint)p * 0xFBA4C795 + nTweak).ToArray();
- G) p" _; R$ g9 m; e! D4 H- a            this.bits = elements == null ? new BitArray(m) : new BitArray(elements);
9 C! E5 Y: ?$ j            this.bits.Length = m;
; }& B6 d; p* O+ l, S  e9 w4 ?            this.Tweak = nTweak;8 e: ~; E; |& J9 M" z
        }
" S4 W7 ^' N! o; ^5 h! [        public void Add(byte[] element)
8 t! u( [7 n4 X/ i4 f" _$ D7 X        {' _, M9 M! s! z  l& u# _, C: F! ]) L
            foreach (uint i in seeds.AsParallel().Select(s => element.Murmur32(s)))! Y% t& l8 q5 E$ f0 A' e
                bits.Set((int)(i % (uint)bits.Length), true);
# V, c+ M8 L" ~' I' y/ ]        }
6 f% q/ @/ p4 c) S6 `        public bool Check(byte[] element)
- o3 L. K( C8 H/ l- |0 |8 b        {
8 z7 a8 e  ^4 ~9 |            foreach (uint i in seeds.AsParallel().Select(s => element.Murmur32(s)))7 c+ \$ O: \+ F
                if (!bits.Get((int)(i % (uint)bits.Length)))
& O' A' _# r8 J: l: M5 y! N7 X! |                    return false;" i  e' v# I6 r7 N7 ~8 c
            return true;9 |! Z7 ?0 W1 j+ e& ]& F
        }7 f& b8 V9 g& B3 k5 d# L2 z
        public void GetBits(byte[] newBits)
5 l6 p" }* P# ^" f# Z        {
6 E! N( m7 i( S6 J            bits.CopyTo(newBits, 0);, `: S$ o' U3 \! o: ]& J9 Y7 Q
        }2 X" b  C. ?3 z) B% f
    }
6 q  ]: `/ l# W* N}
) k5 ]: }8 H# o6 E% n前面讲了这么多,代码竟然这么短,分析分析。
- C7 d+ }# d' e0 e! t构造函数传入了m(多少位),k(hash函数种类),这个和我们前面分析根据p(错误率),和n(要插入的元素)来构造的思路不一样。所以Neo的这个版本应该是一个简化版本,输入的数据n应该是有范围的,具体的范围我们后面运行整个区块链的时候在观察,现在不知道n的个数有多大。hash函数使用了Murmur32,然后传入不同的seed模拟不同的hash函数,这个是可以的。使用linq,函数式编程代码非常简洁,这也是C#的一个优势啊。add,check函数都很容易看懂,确实实现很简洁。6 X% v+ e7 N4 z
  v5 U, P  B1 j& B7 q! |
总结
) ?$ Z; f0 D1 J1 d/ I+ x$ |/ [Bloom Filter是牛逼的数据结构,因为有很多数学知识在里面,虽然代码不长,但是能看完这篇文章的人,会感受到代码之美。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

丈黑起恋秘 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    2