常規實現
1、數組
2、鏈表
3、樹、平衡二叉樹
4、Map(紅黑樹)
5、哈希表
上面的使用方式在結合常見的排序方式比如二分,可以快速的查找數據是否存在,但當集合數據元素非常大,比如1億條,這個時候,數據結構問題就會凸顯出來,數組,鏈表等 ,就會非常喫內存,內存的消耗會成指數級增長,最終達到瓶頸。對於哈希表,其實也是很佔用內存,哈希表的常規做法是key值映射成一個8個字節的信息指紋,對於數據量過大,問題也是依舊存在,只是相對其他方式能夠從某種層面說節省空間
哈希函數
哈希函數的概念是:將任意大小的數據轉換成特定大小的數據的函數,轉換後的數據稱爲哈希值或哈希編碼。下面是一幅示意圖:
可以明顯的看到,原始數據經過哈希函數的映射後稱爲了一個個的哈希編碼,數據得到壓縮。哈希函數是實現哈希表和布隆過濾器的基礎。
布隆過濾器介紹
- 巴頓.布隆於一九七零年提出
- 一個很長的二進制向量 (位數組)
- 一系列隨機函數 (哈希) 空間效率和查詢效率高
- 有一定的誤判率(哈希表是精確匹配)
布隆過濾器原理
布隆過濾器(Bloom Filter)的核心實現是一個超大的位數組和幾個哈希函數。假設位數組的長度爲m,哈希函數的個數爲k
以上圖爲例,具體的操作流程:
假設集合裏面有3個元素{x, y, z},哈希函數的個數爲3。首先將位數組進行初始化,將裏面每個位都設置位0。對於集合裏面的每一個元素,將元素依次通過3個哈希函數進行映射,每次映射都會產生一個哈希值,這個值對應位數組上面的一個點,然後將位數組對應的位置標記爲1。查詢W元素是否存在集合中的時候,同樣的方法將W通過哈希映射到位數組上的3個點。如果3個點的其中有一個點不爲1,則可以判斷該元素一定不存在集合中。反之,如果3個點都爲1,則該元素可能存在集合中。注意:此處不能判斷該元素是否一定存在集合中,可能存在一定的誤判率。可以從圖中可以看到:假設某個元素通過映射對應下標爲4,5,6這3個點。雖然這3個點都爲1,但是很明顯這3個點是不同元素經過哈希得到的位置,因此這種情況說明元素雖然不在集合中,也可能對應的都是1,這是誤判率存在的原因。
布隆過濾器添加元素
將要添加的元素給k個哈希函數
得到對應於位數組上的k個位置
將這k個位置設爲1
布隆過濾器查詢元素
將要查詢的元素給k個哈希函數
得到對應於位數組上的k個位置
如果k個位置有一個爲0,則肯定不在集合中
如果k個位置全部爲1,則可能在集合中
golang版本的代碼
package main
import (
"fmt"
"github.com/willf/bitset"
"math/rand"
)
func main() {
Foo()
bar()
}
func Foo() {
var b bitset.BitSet // 定義一個BitSet對象
b.Set(1).Set(2).Set(3) //添加3個元素
if b.Test(2) {
fmt.Println("2已經存在")
}
fmt.Println("總數:", b.Count())
b.Clear(2)
if !b.Test(2) {
fmt.Println("2不存在")
}
fmt.Println("總數:", b.Count())
}
func bar() {
fmt.Printf("Hello from BitSet!\n")
var b bitset.BitSet
// play some Go Fish
for i := 0; i < 100; i++ {
card1 := uint(rand.Intn(52))
card2 := uint(rand.Intn(52))
b.Set(card1)
if b.Test(card2) {
fmt.Println("Go Fish!")
}
b.Clear(card1)
}
// Chaining
b.Set(10).Set(11)
for i, e := b.NextSet(0); e; i, e = b.NextSet(i + 1) {
fmt.Println("The following bit is set:", i)
}
// 交集
if b.Intersection(bitset.New(100).Set(10)).Count() == 1 {
fmt.Println("Intersection works.")
} else {
fmt.Println("Intersection doesn't work???")
}
}
//----------------------------------------------------------------------------
// @ Copyright (C) free license,without warranty of any kind .
// @ Author: hollson <[email protected]>
// @ Date: 2019-12-06
// @ Version: 1.0.0
//------------------------------------------------------------------------------
package bloomx
import "github.com/willf/bitset"
const DEFAULT_SIZE = 2<<24
var seeds = []uint{7, 11, 13, 31, 37, 61}
type BloomFilter struct {
Set *bitset.BitSet
Funcs [6]SimpleHash
}
func NewBloomFilter() *BloomFilter {
bf := new(BloomFilter)
for i:=0;i< len(bf.Funcs);i++{
bf.Funcs[i] = SimpleHash{DEFAULT_SIZE,seeds[i]}
}
bf.Set = bitset.New(DEFAULT_SIZE)
return bf
}
func (bf BloomFilter) Add(value string){
for _,f:=range(bf.Funcs){
bf.Set.Set(f.hash(value))
}
}
func (bf BloomFilter) Contains(value string) bool {
if value == "" {
return false
}
ret := true
for _,f:=range(bf.Funcs){
ret = ret && bf.Set.Test(f.hash(value))
}
return ret
}
type SimpleHash struct{
Cap uint
Seed uint
}
func (s SimpleHash) hash(value string) uint{
var result uint = 0
for i:=0;i< len(value);i++{
result = result*s.Seed+uint(value[i])
}
return (s.Cap-1)&result
}