BitSet和布隆过滤器(Bloom Filter)

本文介绍了Bloom Filter的概念,这是一种用于判断元素是否存在于集合中的数据结构,具有高空间效率和时间效率,但可能存在误判。Bloom Filter在Elasticsearch、Guava和Hadoop等框架中有实现。同时,文章探讨了BitSet的基本原理,它是位操作的对象,用于存储0或1,适合表示大量数据的出现状态。BitSet内部会动态扩充以适应数据增长。Bloom Filter常见应用场景包括爬虫URL过滤、日志分析和用户数统计等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

布隆过滤器

Bloom Filter 是由Howard Bloom 在 1970 年提出的二进制向量数据结构,它具有很好的空间和时间效率,被用来检测一个元素是不是集合中的一个成员。如果检测结果为是,该元素不一定在集合中;但如果检测结果为否,该元素一定不在集合中。因此Bloom filter具有100%的召回率。这样每个检测请求返回有“在集合内(可能错误)”和“不在集合内(绝对不在集合内)”两种情况,可见 Bloom filter 是牺牲了正确率和时间以节省空间。

 

当然布隆过滤器也有缺点,主要是误判的问题,随着数据量的增加,误判率也随着增大,解决办法:可以建立一个列表,保存哪些数值是容易被误算的。

Bloom Filter最大的特点是不会存在false negative,即:如果contains()返回false,则该元素一定不在集合中,但会存在一定的true negative,即:如果contains()返回true,则该元素可能在集合中。

Bloom Filter在很多开源框架都有实现,例如:

Elasticsearch:org.elasticsearch.common.util.BloomFilter

guava:com.google.common.hash.BloomFilter

Hadoop:org.apache.hadoop.util.bloom.BloomFilter(基于BitSet实现)

有兴趣可以看看源码。

BitSet的基本原理

最后再了解一下BitSet的基本原理,BitSet是位操作的对象,值只有0或1,内部实现是一个long数组,初始只有一个long数组,所以BitSet最小的size是64,当存储的数据增加,初始化的Long数组已经无法满足时,BitSet内部会动态扩充,最终内部是由N个long来存储,BitSet的内部扩充和List,Set,Map等得实现差不多,而且都是对于用户透明的。
1G的空间,有 8*1024*1024*1024=8589934592bit,也就是可以表示85亿个不同的数。

BitSet用1位来表示一个数据是否出现过,0为没有出现过,1表示出现过。在long型数组中的一个元素可以存放64个数组,因为Java的long占8个byte=64bit,具体的实现,看看源码:

首先看看set方法的实现:

 

public void set(int bitIndex) {
   if (bitIndex < 0)   //set的数不能小于0
        throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

   int wordIndex = wordIndex(bitIndex);//将bitIndex右移6位,这样可以保证每64个数字在long型数组中可以占一个坑。
   expandTo(wordIndex);

   words[wordIndex] |= (1L << bitIndex); // Restores invariants
   checkInvariants();
}

get命令实现:

public boolean get(int bitIndex) {
   if (bitIndex < 0)
       throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

   checkInvariants();

   int wordIndex = wordIndex(bitIndex);//和get一样获取数字在long型数组的那个位置。
   return (wordIndex < wordsInUse)
        && ((words[wordIndex] & (1L << bitIndex)) != 0);//在指定long型数组元素中获取值。
}

BitSet容量动态扩展:

 

private void ensureCapacity(int wordsRequired) {
   if (words.length < wordsRequired) {
        // Allocate larger of doubled size or required size
        int request = Math.max(2 * words.length, wordsRequired);//默认是扩大一杯的容量,如果传入的数字大于两倍的,则以传入的为准。
        // wordsRequired = 传入的数值右移6位 + 1
        words = Arrays.copyOf(words, request);
        sizeIsSticky = false;
   }
}

 

BitSet中实现了Cloneable接口,并定义在表中列出的方法:

SNMethods with 描述
1void and(BitSet bitSet)
与运算调用的内容BitSet中对象与那些指定bitSet。结果存放到调用对象。
2void andNot(BitSet bitSet)
对于bitSet每1位,在调用BitSet中的相应位清零。
3int cardinality( )
返回BitSet的容量。
4void clear( )
所有位清零。
5void clear(int index)
index指定的位清零。
6void clear(int startIndex, int endIndex)
将从startIndex到endIndex清零。
7Object clone( )
重复调用BitSet中对象。
8boolean equals(Object bitSet)
返回true如果调用位设置相当于一个在bitSet通过。否则,该方法返回false。
9void flip(int index)
逆转由index指定的位。 
10void flip(int startIndex, int endIndex)
反转将从startIndex位到endIndex.
11boolean get(int index)
返回指定索引处的位的当前状态。
12BitSet get(int startIndex, int endIndex)
返回一个BitSet中,它包含的比特将从startIndex到endIndex.1。调用对象不被改变。
13int hashCode( )
返回调用对象的哈希代码。
14boolean intersects(BitSet bitSet)
如果至少有一个对调用对象和bitSet内相应位为1,则返回true。
15boolean isEmpty( )
返回true如果在调用对象中的所有位均为零。
16int length( )
返回到持有调用BitSet中的内容所需的比特数。这个值是由最后1位的位置决定的。
17int nextClearBit(int startIndex)
返回下个清零位的索引,(即,下一个零位),从由startIndex指定的索引开始
18int nextSetBit(int startIndex)
返回下一组位(即,下一个1比特)的索引,从由startIndex指定的索引开始。如果没有位被设置,则返回1。
19void or(BitSet bitSet)
OR值调用的内容BitSet中对象,通过BitSet指定。结果被放置到调用对象。 
20void set(int index)
设置由index指定的位。
21void set(int index, boolean v)
设置由index指定在v. true为传递的值的位设置位,false则清除该位。
22void set(int startIndex, int endIndex)
设置位将从startIndex到endIndex.1。
23void set(int startIndex, int endIndex, boolean v)
设置位从startIndex到endIndex.1,在真正传递的值v设置位,清除位为false。
24int size( )
返回位在调用BitSet中对象的数量。
25String toString( )
返回字符串相当于调用BitSet中的对象。
26void xor(BitSet bitSet)

在异或调用BitSet中对象的内容与由BitSet指定。结果存放到调用对象。

 

BloomFilter的使用场景

 

1,爬虫的URL过滤。

2,日志分析

3,用户数统计等等等

总之使用布隆过滤器应该是可能容忍小概率误判的场景,不然慎用。。。

<think>嗯,用户这次在询问C++中布隆过滤器的实现与演示代码,并且引用了站内的几个相关引用内容。根据引用[2],用户已经了解布隆过滤器的基本概念特点,比如二进制向量、随机映射函数、空间效率高等。用户可能正在开发需要高效去重的系统,比如爬虫URL检测或缓存系统。用户提供的引用中包含线程安全的LRU缓存代码(引用[1])、布隆过滤器理论说明(引用[2])、排序算法实现(引用[3])以及位图操作(引用[4])。其中引用[4]的位图重置函数reset()尤其重要,因为位图正是布隆过滤器的核心存储结构。布隆过滤器的实现关键点有三个:位数组大小(m)、哈希函数数量(k)、哈希种子选择。引用[2]提到布隆过滤器的误差概率特性,这提醒我需要强调参数设计对误判率的影响。用户作为C++开发者,应该需要可运行的实际代码而非伪代码。在回复中,应当包含位数组的实现(参考引用[4]的位操作技术)、多个哈希函数设计(如不同种子)、插入查询接口。由于用户引用过线程安全代码(引用[1]),可能需要考虑并发场景下的加锁机制。但核心演示可暂不涉及线程安全,保持代码简洁。最后需要提醒用户布隆过滤器的典型缺陷:不可删除、存在误判。这可以通过引用[2]提到的“概率性结果”来佐证。实践建议部分将补充空间计算场景选择,避免用户误用于需要精确删除的场景。</think> 以下是C++中布隆过滤器的实现与演示代码,结合引用内容中的技术要点解析: ### 布隆过滤器核心实现 ```cpp #include <bitset> #include <functional> #include <vector> class BloomFilter { private: std::bitset<10000> bit_array; // 位数组(引用[4]的位操作基础) std::vector<std::hash<std::string>> hash_functions; // 哈希函数集合(引用[2]的"一系列随机映射函数") public: BloomFilter(int hash_count) { for (int i = 0; i < hash_count; ++i) { // 使用不同种子创建多个哈希函数(降低误判率) std::hash<std::string> hash_func; hash_functions.push_back(hash_func); } } // 添加元素(引用[2]的"高效插入") void add(const std::string& item) { for (auto& hash_func : hash_functions) { size_t hash_val = hash_func(item); size_t index = hash_val % bit_array.size(); bit_array.set(index); // 置位对应位置(引用[4]的位操作) } } // 检查元素(引用[2]的"可能存在/一定不存在"特性) bool contains(const std::string& item) const { for (auto& hash_func : hash_functions) { size_t hash_val = hash_func(item); size_t index = hash_val % bit_array.size(); if (!bit_array.test(index)) { return false; // 任一位置未置位则必然不存在 } } return true; // 所有位置均置位则可能存在(有误判概率) } }; ``` ### 演示代码 ```cpp #include <iostream> int main() { // 创建含3个哈希函数的布隆过滤器(引用[2]的"概率性结果"特性) BloomFilter filter(3); filter.add("https://example.com/page1"); filter.add("https://example.com/page2"); std::cout << "Check page1: " << filter.contains("https://example.com/page1") // 输出1 (存在) << std::endl; std::cout << "Check unknown: " << filter.contains("https://example.com/unknown") // 可能输出0或1 (存在误判概率) << std::endl; } ``` ### 核心特性解析(引用[2]的要点) 1. **空间效率** 位数组存储代替传统数据结构(引用[2]的*"占用空间更少"*) 2. **误差特性** 当`contains()`返回`true`时可能存在误判,返回`false`时元素必然不存在 3. **无删除操作** 直接删除位会导致其他元素失效(引用[2]的*"一般不提供删除操作"*) 4. **哈希函数数量** 增加哈希函数数量可降低误判率,但会增大计算开销(需权衡) ### 参数设计建议 1. 位数组大小 `m` 与预期元素数量 `n` 的关系: $$ m = -\frac{n \ln p}{(\ln 2)^2} $$ (其中 $p$ 为可接受的误判概率) 2. 最优哈希函数数量 `k`: $$ k = \frac{m}{n} \ln 2 $$ ### 实践建议 1. **适用场景**(引用[2]的应用案例): - URL爬虫去重(检测新链接) - 缓存击穿防护(快速过滤无效请求) - 拼写检查器(单词存在性校验) 2. **注意事项**: - 需要预估最大数据量来设定位数组大小 - 对精度要求高的场景需配合二级校验 - C++11+标准中可使用`std::hash`的默认特化[^2] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值