Go 布隆过滤器使用

468 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情

Go 布隆过滤器使用

判断目标值是否在一个集合中,我们会想到一个 HashMap 实现,但是当集合存储数据比较多时,此时,map 会消耗大量的内存,此时可能会考虑 bitmap,但是 bitmap 只能保存整型数据,对于字符串类型,除非把字符串映射成整型数据。

布隆过滤器

布隆过滤器是布隆(Burton Howard Bloom)在1970年提出的,布隆过滤器由一个 BitArray(长度为m的位数组)和k个哈希函数组成,布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率。

所谓错误识别率是指:如果Bloom Filter 报告元素不存在,那么该元素一定不在集合中, 如果 Bloom Fliter 判断存在,元素可能不在集合中。

使用场景

在缓存穿透,垃圾邮件识别可能有比较好的效果。

算法说明

  1. 长度为m 的位数组初始化值为0, hash 函数应该是尽量把数据均匀散列

  2. 插入一个元素,将元素数据分别输入 K 个hash 函数,产生 K 个 hash 值,以 hash 值作为 bit数组下标。将下标对应位置记录为1

  3. 当查询数据时,将元素输入 hash 函数,然后查询对应的 K 个 bit位,如果有任意一个bit 位置 为0 ,说明该元素一定不在集合中,如果比特位置都为1 ,说明该元素有较大可能性存在集合中。

代码实现

package bloom

import (
	"fmt"
	"testing"

	"github.com/bits-and-blooms/bitset"
)

//设置哈希数组默认大小为16
const DefaultSize = 16

//设置种子,保证不同哈希函数有不同的计算方式
var seeds = []uint{7, 11, 13, 31, 37, 61}

//布隆过滤器结构,包括二进制数组和多个哈希函数
type BloomFilter struct {
	//使用第三方库
	set *bitset.BitSet
	//指定长度为6
	hashFuncs [6]func(seed uint, value string) uint
}

//构造一个布隆过滤器,包括数组和哈希函数的初始化
func NewBloomFilter() *BloomFilter {
	bf := new(BloomFilter)
	// 长度16默认bit array
	bf.set = bitset.New(DefaultSize)

	// 构造 6 个 hash 函数
	for i := 0; i < len(bf.hashFuncs); i++ {
		bf.hashFuncs[i] = createHash()
	}
	return bf
}

//构造6个哈希函数,每个哈希函数有参数seed保证计算方式的不同
func createHash() func(seed uint, value string) uint {
	return func(seed uint, value string) uint {
		var result uint = 0
		for i := 0; i < len(value); i++ {
			result = result*seed + uint(value[i])
		}
		//length = 2^n 时,X % length = X & (length - 1)
		return result & (DefaultSize - 1)
	}
}

//添加元素
func (b *BloomFilter) add(value string) {
	for i, f := range b.hashFuncs {
		//将哈希函数计算结果对应的数组位置1
		b.set.Set(f(seeds[i], value))
	}
}

// 查询元素是否存在
func (b *BloomFilter) contains(value string) bool {
	//调用每个哈希函数,并且判断数组对应位是否为1
	//如果不为1,直接返回false,表明一定不存在
	for i, f := range b.hashFuncs {
		//result = result && b.set.Test(f(seeds[i], value))
		if !b.set.Test(f(seeds[i], value)) {
			return false
		}
	}
	return true
}

func TestBloomFilter(t *testing.T) {
	filter := NewBloomFilter()
	filter.add("hello")
	filter.add("world")
	fmt.Println(filter.contains("hello"))
	fmt.Println(filter.contains("world"))
	fmt.Println(filter.contains("xiao"))
	fmt.Println(filter.contains("xiaoming")) // 出现误判
}

测试结果:

=== RUN   TestBloomFilter
true
true
false
true
--- PASS: TestBloomFilter (0.00s)
PASS

参考资料