Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

BloomFilter布隆过滤器 简介

丈黑起恋秘
91 0 0
布隆过滤器; G  O- z& H0 d' X/ G9 x: R
布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
6 d* v1 Z1 v' l6 R# ?% w+ L布隆过滤器 (Bloom Filter)是一种space efficient的概率型数据结构,在垃圾邮件过滤的黑白名单方法、爬虫(Crawler)的网址判重模块中等等经常被用到。哈希表也能用于判断元素是否在集合中,但是布隆过滤器只需要哈希表的1/8或1/4的空间复杂度就能完成同样的问题。布隆过滤器可以插入元素,但不可以删除已有元素。其中的元素越多,false positive rate(误报率)越大,但是false negative (漏报)是不可能的。
; s6 q) f' u* }2 Y1 J基本概念
# D4 s; }2 J/ x* K- J: e4 b+ m如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为 O(n),O(log n),O(n/k)。$ t9 t9 k  l# p' l- y% N
布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。
: c8 D; A" a2 C算法描述
! T" X% M+ j' x, h5 P/ Z2 ]一个empty bloom filter是一个有m bits的bit array,每一个bit位都初始化为0。并且定义有k个不同的hash function,每个都以uniform random distribution将元素hash到m个不同位置中的一个。在下面的介绍中n为元素数,m为布隆过滤器或哈希表的slot数,k为布隆过滤器重hash function数。1 A' C8 P! e6 i5 P% q  g
为了add一个元素,用k个hash function将它hash得到bloom filter中k个bit位,将这k个bit位置1。. F! X. L. t; V- G) d
为了query一个元素,即判断它是否在集合中,用k个hash function将它hash得到k个bit位。若这k bits全为1,则此元素在集合中;若其中任一位不为1,则此元素比不在集合中(因为如果在,则在add时已经把对应的k个bits位置为1)。# e6 A: _' g. R
不允许remove元素,因为那样的话会把相应的k个bits位置为0,而其中很有可能有其他元素对应的位。因此remove会引入false negative,这是绝对不被允许的。
( B( a$ j& P) j; l当k很大时,设计k个独立的hash function是不现实并且困难的。对于一个输出范围很大的hash function(例如MD5产生的128 bits数),如果不同bit位的相关性很小,则可把此输出分割为k份。或者可将k个不同的初始值(例如0,1,2, … ,k-1)结合元素,feed给一个hash function从而产生k个不同的数。
/ |9 K$ j& D) q; k8 V, E  p' p当add的元素过多时,即n/m过大时(n是元素数,m是bloom filter的bits数),会导致false positive过高,此时就需要重新组建filter,但这种情况相对少见。
* {+ c% S+ Z* b0 }优点) x; E" I3 ~8 Z9 w' p8 |- O: J
相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数(O(k))。另外,散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。/ C! h2 b2 y1 {; \  g
布隆过滤器可以表示全集,其它任何数据结构都不能;
3 O& P" R1 [( N! ?8 M2 g$ D3 u' ]缺点
; K( @' @. [. c- _但是布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。
/ z2 @. h5 c& S- ~( L! G: I# D另外,一般情况下不能从布隆过滤器中删除元素。我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面。这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。
% p- _. U! W- M! g在降低误算率方面,有不少工作,使得出现了很多布隆过滤器的变种。
% M$ S" I+ Q- C5 r举例说明布隆过滤器的空间优势
! P& D" O7 Y' t$ n7 |先来一个结论:对于一个有1%误报率和一个最优k值的布隆过滤器来说,无论元素的类型及大小,每个元素只需要9.6 bits来存储。这个优点一部分继承自array的紧凑性,一部分来源于它的概率性。如果你认为1%的误报率太高,那么对每个元素每增加4.8 bits,我们就可将误报率降低为原来的1/10。add和query的时间复杂度都为O(k),与集合中元素的多少无关,这是其他数据结构都不能完成的。k是hash函数的个数。
4 e8 i  u- N9 y7 a! N4 t. p' E举例: 现有1亿个email的黑名单,元素的数量(即email列表)为 108。若采用布隆过滤器,取k=8(k为hash函数个数)。因为n为1亿,所以总共需要8*108。又因为在保证误判率低(后面解释)且k和m选取合适时,空间利用率为50%(后面会解释),所以总空间为  C8 e0 G, U( a' C3 ^! a
: h6 O( {+ h+ d  G3 I/ A- H% Z
空间优势
: S2 L. u  C9 x% B所需空间比上述哈希结构或者数组小得多,并且误判率在万分之一以下。为什么可以这样算,可以看下面。
+ c  }# N( J: I! Z* l误判概率的证明和计算
  Y. E1 @7 u4 J) ?该过程的详细说明来自于这个文章http://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html,为了看懂求导过程,需要复习数学知识。( ~' b& H+ Q/ ^0 Z) w; S
对某一特定bit位在一个元素由某特定hash function插入时没有被置位为1的概率为:
/ N8 i' |& {, J1 b
: v) F' p( K2 N: B  b1 L8 h' F2 B则k个hash function中没有一个对其置位的概率为:
! B" C. c, J: i/ R7 U: d3 M7 D+ x( w
如果插入了n个元素,但都未将其置位的概率为:0 W( Z; r( b: q4 x6 G+ Q3 W9 F
" b/ C1 t) |& T  k0 J  I/ i
现在考虑query阶段,若对应某个待query元素的k bits全部置位为1,则可判定其在集合中。因此将某元素误判的概率为:
7 s5 @) d0 a9 u0 q7 V2 P
- ^) j7 `3 l. q1 @0 X6 @由于
8 e% S& x7 {6 m/ s6 c# e/ K# H9 _% y4 y
,并且1/m 当m很大时趋近于0,所以, ~% m, Q( l5 J3 Z( W3 r/ r2 @6 R
现在计算对于给定的m和n,k为何值时可以使得误判率最低。设误判率为k的函数为:
. a5 \7 O" x4 O' e$ i2 Q0 l3 B# l. e4 C% d0 s

, i& z8 n! [+ g# g* ?# ]# d7 u
; t3 }$ s& l/ t) }: ]则简化为4 k! @+ q- {$ y2 N( ^! n2 |, t
; b0 |# k4 w! N+ K5 P- S6 J
因为等式右边的底数上是函数,指数上也是函数,没有方法求这样组合函数的导数,只能取对数之后,变成乘法。我们有两个函数相乘的求导方法,求导的几个方法可以看参考资料,有很好的视频说明。0 v5 O; [. H9 h! C0 w8 p
两边取对数得/ q- d# `* H7 H  s: W0 C

+ _) E+ y% D$ w3 [3 w( \4 J两边对k求导得,这边涉及到乘法求导,对数求导,幂函数求导:( E1 p9 F* T- W  D6 d7 d3 a9 X+ m

( U/ D: W& S6 k1 I, D% ~* j' g下面求最值,
6 ~) Z3 b, H# l# S5 ^( w: n" J0 G& y4 \- X1 _3 D$ I4 s$ o  D% c
红圈中的等式是把两边看成xln(x)这种形式得到的,和该函数的单调性相关。数学上能不能这么操作我还不太清楚。数学好的大神可以留言解释一下。
/ w9 k2 M4 }9 G# ^' b  [- Q1 i因此,即当; ]/ G2 {1 t2 @2 E+ P" j

. @6 @5 F; `! U5 D时误判率最低,此时误判率为:
) a& K: c* }( _
6 |% t1 E  h0 }* V可以看出若要使得误判率≤1/2,则:0 Y2 Q( X2 z1 z& }- b3 F3 z. x

' l) \) e$ K0 K9 \& C( C9 _这说明了若想保持某固定误判率不变,布隆过滤器的bit数m与被add的元素数n应该是线性同步增加的。
1 o! o2 V& V0 r设计和应用布隆过滤器的方法
7 _# `6 \: T3 p' A. N9 w1 T应用时首先要先由用户决定要add的元素数n和希望的误差率P。这也是一个设计完整的布隆过滤器需要用户输入的仅有的两个参数,之后的所有参数将由系统计算,并由此建立布隆过滤器。4 F- ^2 D9 E1 L; [5 [. j* y
系统首先要计算需要的内存大小m bits:# N, S* M* h- T$ h

, D* p' M! a* h8 x+ z再由m,n得到hash function的个数:' q8 V- Q; Z9 ^" O6 p

, C" c1 ?$ b2 p% Z至此系统所需的参数已经备齐,接下来add n个元素至布隆过滤器中,再进行query。
- i+ U7 {5 }. G* q4 t& w* v" D根据公式,当k最优时:! @8 w* O+ I) `& g6 X: D
2 d6 ^$ z$ s2 |
因此可验证当P=1%时,存储每个元素需要9.6 bits:. W- K: k) r5 X

/ I1 f. y; m! U6 f而每当想将误判率降低为原来的1/10,则存储每个元素需要增加4.8 bits:
) F0 r$ T4 d/ g2 ]. Q5 D
6 [3 P  l: m. u+ _; s6 n这里需要特别注意的是,9.6 bits/element不仅包含了被置为1的k位,还把包含了没有被置为1的一些位数。此时的
' ]/ u: {1 f7 y! h8 Y8 H  D3 p  F
' l1 e3 t/ F( u" L  ?! ^才是每个元素对应的为1的bit位数。
1 y8 A# Z# e2 I' ^- r, I0 K, q$ I0 A% }8 F5 i
从而使得P(error)最小时,我们注意到:
6 z) E1 X6 ^( z; ^3 [$ b5 `1 I( l) G: w) i' Q/ v3 D( Q* }7 G
中的
6 n) D. |3 Z2 l! V* V  ?,即
* w! g5 P* }' W  U) f
; v7 `- g) M/ |) E# I: |3 Z此概率为某bit位在插入n个元素后未被置位的概率。因此,想保持错误率低,布隆过滤器的空间使用率需为50%。
" ~$ S% `% F" u# zNeo中的布隆过滤器
! ~8 B5 e; E1 s4 C上面的内容大部分抄袭http://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html,原作者写的太好了,我只是加上一些我的理解,方便数学不好的道友理解。下面我们看看Neo中的Bloom Filter。& r: Z( h- S; k. w) U
using System.Collections;
( G: A! |* _. N' B, [8 vusing System.Linq;  F' u' V0 p( P- \* \
namespace Neo.Cryptography
9 v/ }$ J2 r' T! g. U# x{7 ?& [- t6 Y3 C5 w+ m
    public class BloomFilter4 j/ R# Y" d* D5 o
    {
# G3 s& l' w0 B& j% Q  H6 H        private readonly uint[] seeds;
0 u( ^) M% [- L1 @; n# E% J        private readonly BitArray bits;2 d& Q7 r! k  X# Y$ ?& W* P% ~
        public int K => seeds.Length;/ T, f- o1 Z1 ?) |! z( y6 V7 Y
        public int M => bits.Length;+ F4 F; V5 q- |0 t5 V6 f
        public uint Tweak { get; private set; }
' U/ W. X5 f& [9 @4 H4 s& Z        public BloomFilter(int m, int k, uint nTweak, byte[] elements = null)3 h2 a. K' x8 |* @9 O! q* s  W
        {
$ E. f8 `1 Z* S, x* i            this.seeds = Enumerable.Range(0, k).Select(p => (uint)p * 0xFBA4C795 + nTweak).ToArray();+ x# w4 [" c, r
            this.bits = elements == null ? new BitArray(m) : new BitArray(elements);
0 I# x% t* E" d+ j2 b) w  `            this.bits.Length = m;
, `7 X- Q( |! R3 Z7 A- \# o            this.Tweak = nTweak;
4 Q6 @& _# v6 _5 ?+ ~: r6 {        }2 f6 R) E- @" d* Y+ e% P0 W
        public void Add(byte[] element)$ c" L9 u5 p4 p5 s2 T7 J  G0 J
        {: s3 I" H2 c2 ?% e0 c  L' s
            foreach (uint i in seeds.AsParallel().Select(s => element.Murmur32(s)))! C: D  I! B8 m. X, _* m6 e6 J
                bits.Set((int)(i % (uint)bits.Length), true);
4 E( d6 w1 @" k8 W5 C        }
0 B( a4 c1 }3 ?; H. N1 w        public bool Check(byte[] element)6 G8 q. Y( c0 L# E. J. V; s
        {
8 a; M! k) d) A/ s5 z7 V            foreach (uint i in seeds.AsParallel().Select(s => element.Murmur32(s)))1 M5 h7 M! Z3 S3 a- C5 ?5 {+ R4 c
                if (!bits.Get((int)(i % (uint)bits.Length)))2 s* c, v$ U3 Y5 B+ F
                    return false;4 Q) J- E+ p' M9 {/ }! r
            return true;
$ v6 B8 I& Y$ w* L0 U1 Z% S        }2 N2 |+ Y1 ?8 _
        public void GetBits(byte[] newBits); E& C! v( _: R
        {
# V& B9 m. n8 n% X* N: u  D            bits.CopyTo(newBits, 0);" }: c4 g2 R# j& g/ R6 N
        }
& \) R  U. y! p/ g$ F    }0 b6 `/ F7 P+ n" V" V+ R& a
}8 `: ^+ m" B, Y- d  D
前面讲了这么多,代码竟然这么短,分析分析。
* n4 y9 l- f( m  E0 U% s# J构造函数传入了m(多少位),k(hash函数种类),这个和我们前面分析根据p(错误率),和n(要插入的元素)来构造的思路不一样。所以Neo的这个版本应该是一个简化版本,输入的数据n应该是有范围的,具体的范围我们后面运行整个区块链的时候在观察,现在不知道n的个数有多大。hash函数使用了Murmur32,然后传入不同的seed模拟不同的hash函数,这个是可以的。使用linq,函数式编程代码非常简洁,这也是C#的一个优势啊。add,check函数都很容易看懂,确实实现很简洁。
2 J8 ]% e1 T# _, S9 {/ x' m9 S" s0 D5 U
总结* o9 m  A. n0 v( Y- h
Bloom Filter是牛逼的数据结构,因为有很多数学知识在里面,虽然代码不长,但是能看完这篇文章的人,会感受到代码之美。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

丈黑起恋秘 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    2