一文搞懂布隆过滤器,附简易版源码实现

简介

布隆过滤器(Bloom Filter)是由美国计算机科学家Burton Howard Bloom在1970年提出的。它的名称就直接来源于发明者的姓氏"Bloom"。

布隆过滤器是一种空间效率极高的概率型数据结构,它可以用来检测一个元素是否在一个集合中。虽然布隆过滤器允许有一定的误报率(即错误地判断某个元素存在于集合中),但它的设计确保了不存在假阴性(即如果它说某元素不在集合中,那么这个元素绝对不在集合中)。

由于这个特性,布隆过滤器在需要节省空间且能接受一定误判率的场景下非常有用。

布隆的这一发明在后来的几十年中被广泛应用于计算机科学的各个领域,特别是在处理大量数据且对存储空间有严格要求的场景中。布隆过滤器的简洁高效使其成为解决特定问题的理想选择,尤其是在需要快速判断一个元素是否属于一个巨大集合时。

使用场景案例

网络爬虫的URL去重:网络爬虫在抓取网页时,需要避免重复访问同一URL。由于互联网上的URL数量极其庞大,使用传统的数据结构存储已访问的URL会占用大量内存。布隆过滤器可以有效减少内存占用,通过将URL通过哈希函数映射到位数组上,快速判断一个URL是否已被访问过。

垃圾邮件过滤:邮件服务提供商可以使用布隆过滤器来过滤垃圾邮件。将已知的垃圾邮件发送者的地址添加到布隆过滤器中,当收到新邮件时,快速检查发件人地址是否在过滤器中,以决定是否为垃圾邮件。这样可以在不需要存储和查询完整垃圾邮件地址列表的情况下,快速过滤大部分垃圾邮件。

数据库查询缓存:在大规模分布式数据库系统中,为了减少对数据库的查询压力,可以使用布隆过滤器作为一级快速查询缓存。在进行数据库查询之前,先通过布隆过滤器检查数据是否存在。如果布隆过滤器表示数据不存在,就无需进行实际的数据库查询,从而减少了数据库的访问次数和提高了系统的整体性能。

布隆过滤器通过牺牲准确性换取了空间效率,非常适用于那些可以容忍一定误判率的场景,尤其是在处理大量数据且需要快速查询的情况下。

实现原理

布隆过滤器的实现依赖于一个比元素数量大得多的位数组(通常初始化为0)和几个哈希函数。实现过程如下:

  1. 初始化:开始时,布隆过滤器是一个充满0的位数组。
  2. 添加元素:当添加一个元素时,使用多个哈希函数对该元素进行哈希操作,得到几个值。将位数组中对应这几个值的位置设为1。
  3. 查询元素:要判断一个元素是否在集合中,同样使用这几个哈希函数对该元素进行哈希,并检查位数组中这几个位置。如果所有的位置都是1,那么认为元素可能在集合中;如果任一位置是0,则元素一定不在集合中。

用Go实现一个简易布隆过滤器

要用Go实现一个简易的布隆过滤器,我们可以遵循以下步骤:

  1. 定义布隆过滤器结构:包括一个位数组和几个哈希函数。
  2. 实现添加(Add)功能:用于向过滤器中添加元素。
  3. 实现检查(Check)功能:用于检查元素是否可能在集合中。

由于Go标准库中没有直接的位数组实现,我们可以使用big.Int来模拟位数组。同时,为了简化,我们可以使用Go标准库中的crypto/sha256作为哈希函数,并通过一些简单的变换来模拟多个哈希函数的效果。

以下是一个简易布隆过滤器的Go实现示例:

go 复制代码
package main

import (
    "crypto/sha256"
    "math/big"
)

// BloomFilter 布隆过滤器的结构
type BloomFilter struct {
    bitset *big.Int   // 用大整数模拟位数组
    k      uint       // 哈希函数的数量
}

// NewBloomFilter 创建一个新的布隆过滤器
func NewBloomFilter(k uint) *BloomFilter {
    return &BloomFilter{
        bitset: big.NewInt(0),
        k:      k,
    }
}

// Add 向过滤器中添加元素
func (bf *BloomFilter) Add(item []byte) {
    hashes := bf.hash(item)
    for _, hash := range hashes {
        bf.bitset.SetBit(bf.bitset, int(hash), 1)
    }
}

// Check 检查元素是否可能在集合中
func (bf *BloomFilter) Check(item []byte) bool {
    hashes := bf.hash(item)
    for _, hash := range hashes {
        if bf.bitset.Bit(int(hash)) == 0 {
            return false // 如果有任何一个位是0,则元素绝对不在集合中
        }
    }
    return true // 可能在集合中
}

// hash 使用SHA-256哈希函数,并通过简单的变换模拟k个哈希函数
func (bf *BloomFilter) hash(item []byte) []uint {
    hash1 := sha256.Sum256(item)
    hash2 := sha256.Sum256(hash1[:])
    hashes := make([]uint, bf.k)
    for i := uint(0); i < bf.k; i++ {
        hash := big.NewInt(0)
        hash.SetBytes(hash1[:])
        hash.Add(hash, big.NewInt(int64(i)))
        hash.Xor(hash, big.NewInt(0).SetBytes(hash2[:]))
        hashes[i] = uint(hash.Int64()) % 1000 // 为简化起见,这里假设位数组的大小是1000
    }
    return hashes
}

func main() {
    bf := NewBloomFilter(3) // 使用3个哈希函数
    item := []byte("hello")
    bf.Add(item)
    if bf.Check(item) {
        println("Item is possibly in the set.")
    } else {
        println("Item is definitely not in the set.")
    }
}

这个实现非常基础,主要用于演示布隆过滤器的原理。在实际应用中,布隆过滤器的设计需要考虑多方面因素,比如位数组的大小、哈希函数的选择等,以达到预期的误判率和性能要求。此外,上述代码中的哈希函数模拟方法并不是最优的,实际应用中通常会使用专门的哈希函数库来生成多个高质量的哈希值。

相关推荐
熬夜学编程的小王13 分钟前
C++类与对象深度解析(一):从抽象到实践的全面入门指南
c++·git·算法
CV工程师小林15 分钟前
【算法】DFS 系列之 穷举/暴搜/深搜/回溯/剪枝(下篇)
数据结构·c++·算法·leetcode·深度优先·剪枝
Dylanioucn18 分钟前
【分布式微服务云原生】掌握 Redis Cluster架构解析、动态扩展原理以及哈希槽分片算法
算法·云原生·架构
繁依Fanyi27 分钟前
旅游心动盲盒:开启个性化旅行新体验
java·服务器·python·算法·eclipse·tomcat·旅游
罔闻_spider37 分钟前
爬虫prc技术----小红书爬取解决xs
爬虫·python·算法·机器学习·自然语言处理·中文分词
Themberfue1 小时前
基础算法之双指针--Java实现(下)--LeetCode题解:有效三角形的个数-查找总价格为目标值的两个商品-三数之和-四数之和
java·开发语言·学习·算法·leetcode·双指针
陈序缘2 小时前
LeetCode讲解篇之322. 零钱兑换
算法·leetcode·职场和发展
-$_$-2 小时前
【LeetCode HOT 100】详细题解之二叉树篇
数据结构·算法·leetcode
大白飞飞2 小时前
力扣203.移除链表元素
算法·leetcode·链表
学无止境\n2 小时前
[C语言]指针和数组
c语言·数据结构·算法