布隆过滤器简介
原理
布隆过滤器是一个长度为
m
m
m 的
0
/
1
0/1
0/1哈希表,等价于
C
p
p
Cpp
Cpp 中的 bitset
,或者是
b
o
o
l
bool
bool 数组
每插入一个数据时,使用
k
k
k 个哈希函数将原数据分解为哈希表中的
k
k
k 个位置,将这
k
k
k 个位置
1
1
1
在检查一个数据是否存在时,使用相同的哈希函数,并检查得出的
k
k
k 个位置,若为
1
1
1 则有很大的概率存在
概率计算
以下为数学推导,可略过
我们想要知道一个元素误判的概率有多少
设一共加入了
n
n
n 个元素,哈希表长度为
m
m
m,那么加入
n
n
n 个元素,每个元素映射
k
k
k 次,某个位置为
1
1
1 的概率为
p
1
=
1
−
(
1
−
1
m
)
n
k
p_1 = 1 - (1 - \frac 1 m)^{nk}
p1=1−(1−m1)nk
设不在其中的元素
x
x
x 映射到了
k
k
k 位置,那么误判当且仅当这
k
k
k 个位置都为
1
1
1
p
=
p
1
k
=
[
1
−
(
1
−
1
m
)
n
k
]
k
p = p_1^k = [1 -(1 - \frac 1 m)^{nk}]^k
p=p1k=[1−(1−m1)nk]k
考虑到实际情况中
n
,
k
n,k
n,k 都很大,用极限进行估算
lim
x
→
0
(
1
+
x
)
1
x
=
e
\lim_{x \to 0}(1 + x)^{\frac 1 x} = e
x→0lim(1+x)x1=e
p
≈
[
1
−
(
1
+
(
−
1
m
)
)
−
m
−
n
k
m
]
k
=
(
1
−
e
−
n
m
k
)
k
p \approx [1 - (1 + (-\frac 1 m))^{-m\frac {-nk} m}]^k = (1 - e^{-\frac {n} m k})^k
p≈[1−(1+(−m1))−mm−nk]k=(1−e−mnk)k
从上式可以看出,当
m
m
m 增大时,误判概率一定减小,
n
n
n 增大时误判概率一定增大
但是对于
k
k
k 我们还不能轻易的看出规律
最优哈希次数
我们想知道当
n
n
n,
m
m
m 固定时,选取什么样的
k
k
k 最优,那么也就是求出
p
p
p 的极值
令
p
′
=
0
p' = 0
p′=0,利用高数常用技巧去对数后求导降幂,可以求出
k
=
(
ln
2
)
m
n
≈
0.69
m
n
k = (\ln 2) \frac m n \approx 0.69 \frac m n
k=(ln2)nm≈0.69nm
其概率为
p
=
2
−
ln
2
m
n
≈
0.618
5
m
n
p = 2 ^ {-\ln 2\frac m n} \approx 0.6185 ^ {\frac m n}
p=2−ln2nm≈0.6185nm
结论
n
,
m
n,m
n,m 固定取
k
=
(
ln
2
)
m
n
≈
0.69
m
n
k = (\ln 2) \frac m n \approx 0.69 \frac m n
k=(ln2)nm≈0.69nm
时最优
p
=
2
−
ln
2
m
n
≈
0.618
5
m
n
p = 2 ^ {-\ln 2\frac m n} \approx 0.6185 ^ {\frac m n}
p=2−ln2nm≈0.6185nm
对于给定
p
p
p 取
m
=
−
n
ln
p
(
ln
2
)
2
m = -\frac{n \ln p}{(\ln2)^2}
m=−(ln2)2nlnp
常见优化
使用 b i t M a p bitMap bitMap 进行空间上的压缩
代码与优化
一些定义
type bitset struct {
size int //data 数组大小
len uint32 // m
k int // 哈希个数
seed []uint32 // 哈希种子(进制哈希)
data []uint64 // bitset本体
}
初始化
生成哈希种子,分配内存
func calcHash(b []byte, seed uint32) uint32 {
res := uint32(0)
for c := range b {
res *= seed
res += uint32(c)
}
return res
}
func calcLen(n int, p float64) int { //n个元素,预期概率为 p 计算bitmap位数
return int(-math.Log(p)*float64(n)*math.Log2E*math.Log2E) + 1
}
func (a *bitset) init(n int, p float64) {
a.len = uint32(calcLen(n, p))
a.k = max(1, int(0.69*float64(a.len)/float64(n)))
a.seed = make([]uint32, a.k)
for i := 0; i < a.k; i++ {
a.seed[i] = uint32(rand.Intn(1919810))
}
a.size = int(a.len)/64 + 1
a.data = make([]uint64, a.size)
}
插入元素
func (a *bitset) Insert(s []byte) {
for i := 0; i < a.k; i++ {
key := calcHash(s, a.seed[i]) % a.len
a.data[key/64] |= 1 << (key & 63)
}
}
校验元素
func (a *bitset) Check(s []byte) bool {
for i := 0; i < a.k; i++ {
key := calcHash(s, a.seed[i]) % a.len
if a.data[key/64]>>(key&63)&1 != 1 {
return false
}
}
return true
}
可以用以下代码进行测试
func main() {
a := new(bitset)
a.init(10, 0.01)
fmt.Println(a.len, a.size)
for i := 1; i <= 10; i++ {
fmt.Println("byte ", []byte{byte(i)})
a.Insert([]byte{byte(i)})
}
fmt.Println(a.Check([]byte{byte(5)}), a.Check([]byte{byte(1), byte(2)}))
}