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

简介

布隆过滤器(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.")
    }
}

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

相关推荐
董董灿是个攻城狮2 小时前
AI视觉连载8:传统 CV 之边缘检测
算法
ray_liang9 小时前
用六边形架构与整洁架构对比是伪命题?
java·架构
AI软著研究员9 小时前
程序员必看:软著不是“面子工程”,是代码的“法律保险”
算法
FunnySaltyFish9 小时前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
Java编程爱好者9 小时前
字节二面:被问“大模型知识过时了怎么解?”,我答“微调”,面试官当场黑脸:“听说过 RAG 吗?”
架构
颜酱10 小时前
理解二叉树最近公共祖先(LCA):从基础到变种解析
javascript·后端·算法
葫芦的运维日志13 小时前
从手动部署到GitOps只需四步
架构
sumuve13 小时前
从100行到1行:我是如何重构IoT设备实时数据通信的?
架构·响应式设计
koddnty14 小时前
c++协程控制流深入剖析
后端·架构
Mintopia14 小时前
Vite 与 Uni-App X 的协作原理:从前端开发到多端运行的桥梁
架构