golang实现简单的布隆过滤器

8 篇文章 0 订阅

关于布隆过滤器的概念性的介绍,我就不多做解释了,可以详细查看一下文章最后的那篇参考资料。

我实现这个布隆过滤器是从三个公式开始的,如下图所示

只有先处理好这些布隆过滤器的参数以后,才方便创建一个布隆过滤器。三个计算公式对应的实现代码如下:

// 计算布隆过滤器位图大小
// elemNum 元素个数
// errorRate 误判率
func CalBloomSize(elemNum uint64, errRate float64) uint64 {
	var bloomBitsSize = float64(elemNum)*math.Log(errRate)/(math.Log(2)*math.Log(2))*(-1)
	return uint64(math.Ceil(bloomBitsSize))
}

// 计算需要的哈希函数数量
// elemNum 元素个数
// bloomSize 布隆过滤器位图大小,单位bit
func CalHashFuncNum(elemNum, bloomSize uint64) uint64 {
	var k = math.Log(2)*float64(bloomSize)/float64(elemNum)
	return uint64(math.Ceil(k))
}

//计算布隆过滤器误判率
// elemNum 元素个数
// bloomSize 布隆过滤器位图大小,单位bit
// hashFuncNum 哈希函数个数
func CalErrRate(elemNum, bloomSize, hashFuncNum uint64) float64 {
	var y = float64(elemNum)*float64(hashFuncNum)/float64(bloomSize)
	return math.Pow(float64(1)-math.Pow(math.E, y*float64(-1)), float64(hashFuncNum))
}

下面我们开始定义布隆过滤器的类型

type Filter struct {
	ElemNum uint64
	BloomSize uint64 //单位bit
	HashFuncNum uint64
	ErrRate float64

	bitMap *bitset.BitSet
	keys map[uint32]bool
}

ElemNum表示布隆过滤器要加入的元素数量

BloomSize表示布隆过滤器使用的bitmap大小,单位是bit

HashFuncNum表示使用的哈希函数数量

ErrRate表示最大的容错率

bitMap用来指向存储bitmap数据的内存

keys用来存储哈希函数用到的随机数。

这里要详细说明一下,因为根据布隆过滤器的定义,把一个元素加入bitmap的过程中会用到多个哈希函数计算对应的多个哈希值,如果数量足够多,我也不可能编写多种哈希函数去处理(如md5,sha1,sha256等),所以我就采用取巧的方法,使用类似哈希加盐的方式来实现多种哈希函数。通过不同的盐,对同一个输入数据,同一个哈希函数,产生多个不同的哈希值。哈希函数实现如下:

func HMACWithSHA128(seed []byte, key []byte) []byte {
	//hmac512 := hmac.New(sha512.New, key)
	hmac512 := hmac.New(sha1.New, key)
	hmac512.Write(seed)
	return hmac512.Sum(nil)
}

接着增加布隆过滤器的三个方法:

// 初始化布隆过滤器
func (f *Filter)Init() {
	//分配布隆过滤器位图
	f.bitMap = bitset.New(uint(f.BloomSize))

	//初始化哈希函数
	//是否是类似HMAC-SHA256那种通过改变passphase值形成不同的哈希函数
	f.keys = make(map[uint32]bool)
	for uint64(len(f.keys)) < f.HashFuncNum {
		randNum, err := rand.Int(rand.Reader, new(big.Int).SetUint64(math.MaxUint32))
		if err != nil {
			panic(err)
		}
		f.keys[uint32(randNum.Uint64())] = true
	}
}

func (f *Filter)Add(elem []byte) {
	var buf [4]byte
	for k := range f.keys {
		binary.LittleEndian.PutUint32(buf[:], k)
		hashResult := new(big.Int).SetBytes(HMACWithSHA128(elem ,buf[:]))
		result := hashResult.Mod(hashResult, big.NewInt(int64(f.BloomSize)))
		//把result对应的bit置1
		f.bitMap.Set(uint(result.Uint64()))
	}
}

// 判断元素是否在集合里面
func (f *Filter)IsContain(elem []byte) bool {
	var buf [4]byte
	for k:=range f.keys {
		binary.LittleEndian.PutUint32(buf[:], k)
		hashResult := new(big.Int).SetBytes(HMACWithSHA128(elem ,buf[:]))
		result := hashResult.Mod(hashResult, big.NewInt(int64(f.BloomSize)))
		//查询result对应的bit是否被置1
		if !f.bitMap.Test(uint(result.Uint64())) {
			return false
		}
	}
	return true
}

分别表示初始化布隆过滤器,把元素加入布隆过滤器中,以及判断一个元素是否在布隆过滤器中。

最后增加布隆过滤器的New函数

func NewFilter(elemNum, bloomSize, hashFuncNum uint64, errRate float64) *Filter {
	return &Filter{ElemNum:elemNum, BloomSize:bloomSize, HashFuncNum:hashFuncNum, ErrRate:errRate}
}

一个简单的布隆过滤器就做好了,下面写几个测试用例测试一下:

func TestBloomFuncs(t *testing.T) {
	var elemNum uint64 = 10000000000
	var errRate  = 0.0001
	var bloomSize = CalBloomSize(elemNum, errRate)
	fmt.Println("bloom size in bit:",bloomSize)//1917011675474
	fmt.Println("bloom size in Gbyte:",bloomSize/1024/1024/1024/8)

	var hashFuncNum = CalHashFuncNum(elemNum, bloomSize)
	fmt.Println("hash functions number:",hashFuncNum)

	errRate = CalErrRate(elemNum, bloomSize, hashFuncNum)
	fmt.Println("error rate:",errRate)
}

func TestBloomFilter(t *testing.T) {
	file, err := os.Open("word-list-large.txt")
	if err != nil {
		t.Error(err)
		return
	}
	defer file.Close()
	var wordlist []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan(){
		wordlist = append(wordlist, scanner.Text())
	}
	var elemNum = uint64(len(wordlist))
	var errRate = 0.0001
	bloomSize := CalBloomSize(elemNum, errRate)
	hashFuncNum := CalHashFuncNum(elemNum,bloomSize)

	filter := NewFilter(elemNum, bloomSize, hashFuncNum, errRate)
	filter.Init()
	for _,elem := range wordlist {
		filter.Add([]byte(elem))
	}

	var testcases = []struct {
		Elem string
		Want bool
	}{
		{"hello", true},
		{"zoo", false},
		{"word", true},
		{"alibaba", false},
	}

	for _,oneCase := range testcases {
		got := filter.IsContain([]byte(oneCase.Elem))
		if got != oneCase.Want {
			if got {
				t.Error("should not contain", oneCase.Elem)
			} else {
				t.Error("contain", oneCase.Elem)
			}
			t.Error("got != want")
			return
		}
	}
}

完整代码可从github获得:https://github.com/lilianwen/bloomfilter

更多测试用例,后续可能会新增,如从大文件读取巨量测试数据进行测试。

参考资料:

https://mp.weixin.qq.com/s?__biz=Mzg3OTA1MTYzOQ==&mid=2247483700&idx=1&sn=44a9dac2c415b222fb9db5a09e4329a9&chksm=cf0b172cf87c9e3a9213325f5879f794754d601ce4bd7dff71b97da14379b0abafb42978bc9b&scene=27#wechat_redirect

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值