布隆过滤器

布隆过滤器简介

布隆过滤器 wikipedia

原理

布隆过滤器是一个长度为 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(1m1)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(1m1)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 x0lim(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))mmnk]k=(1emnk)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)nm0.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=2ln2nm0.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)nm0.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=2ln2nm0.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)}))
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值