这个过滤器你确定会么?

点击蓝色“Java极客技术”关注我哟

加个“星标”,天天早上 08:14,一起快乐成长

640?wx_fmt=jpeg

01、什么是 BloomFilter(布隆过滤器)

布隆过滤器(英语:Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。主要用于判断一个元素是否在一个集合中。通常我们会遇到很多要判断一个元素是否在某个集合中的业务场景,这个时候往往我们都是采用 Hashmap,Set 或者其他集合将数据保存起来,然后进行对比判断,但是如果元素很多的情况,我们如果采用这种方式就会非常浪费空间。这个时候我们就需要 BloomFilter 来帮助我们了。

1.1、BloomFilter 原理

BloomFilter 是由一个固定大小的二进制向量或者位图(bitmap)和一系列(通常好几个)映射函数组成的。布隆过滤器的原理是,当一个变量被加入集合时,通过 K 个映射函数将这个变量映射成位图中的 K 个点,把它们置为 1。查询某个变量的时候我们只要看看这些点是不是都是 1 就可以大概率知道集合中有没有它了,如果这些点有任何一个 0,则被查询变量一定不在;如果都是 1,则被查询变量很可能在。注意,这里是可能存在,不一定一定存在!这就是布隆过滤器的基本思想。

如下图所示,字符串 "ziyou" 在经过四个映射函数操作后在位图上有四个点被设置成了 1。当我们需要判断 “ziyou” 字符串是否存在的时候只要在一次对字符串进行映射函数的操作,得到四个 1 就说明 “ziyou” 是可能存在的。

640?wx_fmt=png

为什么说是可能存在,而不是一定存在呢?那是因为映射函数本身就是散列函数,散列函数是会有碰撞的,意思也就是说会存在一个字符串可能是 “ziyou01” 经过相同的四个映射函数运算得到的四个点跟 “ziyou” 是一样的,这种情况下我们就说出现了误算。另外还有可能这四个点位上的 1 是四个不同的变量经过运算后得到的,这也不能证明字符串 “ziyou” 是一定存在的,如下图框出来的 1 也可能是字符串“张三”计算得到,同理其他几个位置的 1 也可以是其他字符串计算得到。

640?wx_fmt=png

1.2 特性

所以通过上面的例子我们就可以明确

  • 一个元素如果判断结果为存在的时候元素不一定存在,但是判断结果为不存在的时候则一定不存在

  • 布隆过滤器可以添加元素,但是不能删除元素。因为删掉元素会导致误判率增加。

02、使用场景

2.1、网页 URL 去重

我们在使用网页爬虫的时候(爬虫需谨慎),往往需要记录哪些 URL 是已经爬取过的,哪些还是没有爬取过,这个时候我们就可以采用 BloomFilter 来对已经爬取过的 URL 进行存储,这样在进行下一次爬取的时候就可以判断出这个 URL 是否爬取过。

2.2、黑白名单存储

工作中经常会有一个特性针对不同的设备或者用户有不同的处理方式,这个时候可能会有白名单或者黑名单存在,所以根据 BloomFilter 过滤器的特性,我们也可以用它来存在这些数据,虽然有一定的误算率,但是在一定程度上还是可以很好的解决这个问题的。

2.3、小结

除了上面说的两种场景,其实还有很多场景,比如热点数据访问,垃圾邮件过滤等等,其实这些场景的统一特性就是要判断某个元素是否在某个集合中,原理都是一样的。

03、代码实践

3.1、自己实现
package com.test.pkg;	

	
import java.util.BitSet;	

	
/**	
 * <br>	
 * <b>Function:</b><br>	
 * <b>Author:</b>@author ziyou<br>	
 * <b>Date:</b>2019-10-23 23:21<br>	
 * <b>Desc:</b>无<br>	
 */	
public class BloomFilterTest {	

	
    /**	
     * 初始化布隆过滤器的 bitmap 大小	
     */	
    private static final int DEFAULT_SIZE = 2 << 24;	
    /**	
     * 为了降低错误率,这里选取一些数字作为基准数	
     */	
    private static final int[] seeds = {3, 5, 7, 11, 13, 31, 37, 61};	
    /**	
     * 设置 bitmap	
     */	
    private static BitSet bitset = new BitSet(DEFAULT_SIZE);	
    /**	
     * 设置 hash 函数数量	
     */	
    private static HashFunction[] functions = new HashFunction[seeds.length];	

	

	
    /**	
     * 添加数据	
     *	
     * @param value 需求加入的值	
     */	
    public static void put(String value) {	
        if (value != null) {	
            for (HashFunction f : functions) {	
                //计算 hash 值并修改 bitmap 中相应位置为 true	
                bitset.set(f.hash(value), true);	
            }	
        }	
    }	

	
    /**	
     * 判断相应元素是否存在	
     *	
     * @param value 需要判断的元素	
     * @return 结果	
     */	
    public static boolean check(String value) {	
        if (value == null) {	
            return false;	
        }	
        boolean ret = true;	
        for (HashFunction f : functions) {	
            ret = bitset.get(f.hash(value));	
            //一个 hash 函数返回 false 则跳出循环	
            if (!ret) {	
                break;	
            }	
        }	
        return ret;	
    }	

	
    public static void main(String[] args) {	
        String value = "test";	
        for (int i = 0; i < seeds.length; i++) {	
            functions[i] = new HashFunction(DEFAULT_SIZE, seeds[i]);	
        }	
        put(value);	
        System.out.println(check("value"));	
    }	
}	

	
class HashFunction {	

	
    private int size;	
    private int seed;	

	
    public HashFunction(int size, int seed) {	
        this.size = size;	
        this.seed = seed;	
    }	

	
    public int hash(String value) {	
        int result = 0;	
        int len = value.length();	
        for (int i = 0; i < len; i++) {	
            result = seed * result + value.charAt(i);	
        }	
        int r = (size - 1) & result;	
        return (size - 1) & result;	
    }	

	
}	

上面我们自己写了一个简单的 BloomFilter ,通过 put 方法录入数据,通过 check 方法判断元素是否存在,基本能实现功能,代码中注释也写的很清楚,但是自己实现必定效率不高,所以下面我们看下业内大佬帮我们已经实现好的 BloomFilter。

2.4、Guava 中的 BloomFilter
package com.test.pkg;	

	
import com.google.common.hash.BloomFilter;	
import com.google.common.hash.Funnels;	

	
/**	
 * <br>	
 * <b>Function:</b><br>	
 * <b>Author:</b>@author ziyou<br>	
 * <b>Date:</b>2019-10-24 00:17<br>	
 * <b>Desc:</b>无<br>	
 */	
public class BloomFilterTest02 {	

	
    public static void main(String[] args) {	
        BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), 100000, 0.01);	
        for (int i = 0; i < 100000; i++) {	
            bloomFilter.put(i);	
        }	
        System.out.println(bloomFilter.mightContain(1));	
        System.out.println(bloomFilter.mightContain(2));	
        System.out.println(bloomFilter.mightContain(3));	
        System.out.println(bloomFilter.mightContain(100001));	
    }	
}	

Guava 中已经帮我们实现好了 BloomFilter 的代码,我们只需要在使用的地方调用就好。

这里我们简单解释一下构造方法中的后面两个参数,一个是预计包含的数据量,一个是允许的误差值。代码中会根据我们填入的这两个值,自动帮我们计算出数组的大小,以及需要的散列函数个数,如下图。更多详细的内容,读者可以自行去查看源码,我们这里就不介绍了。

640?wx_fmt=png

640?wx_fmt=png

04、总结

这篇文章给大家介绍了 BloomFilter,一个用来判断元素是否存在与某个集合的高效方法,可以在我们日常的工作中运用起来,结合日常工作的场景,可以进行选择。另外欢迎大家到我们《Java极客技术》知识星球一起进步学习。目前已经有 1300+ 星友了,我们在星球等你。

精彩回顾

<think>好的,我需要解决用户关于如何配置过滤器来实现对多个具有扩展ID设备的精准过滤的问题。首先,我需要理解用户的问题。扩展ID通常出现在CAN总线或者其他使用标识符的通信协议中,用于区分不同的设备或消息类型。用户想要设置一个过滤器,能够精确地过滤出多个具有特定扩展ID的设备。 接下来,我需要回忆相关的知识。CAN总线的过滤器配置通常涉及硬件或软件层面的设置,具体取决于使用的控制器和协议。扩展ID通常有29位,而标准ID是11位。过滤器可以设置为掩码模式或列表模式,掩码模式允许指定哪些位需要匹配,而列表模式则是精确匹配特定的ID。 用户提到的是“multiple extended ID devices”,所以需要同时过滤多个不同的扩展ID。这时候可能需要使用多个过滤器或者合理设置掩码来覆盖所有需要的ID。每个过滤器可能只能处理一定数量的ID,因此需要根据硬件支持的过滤器数量来规划配置。 另外,参考用户提供的引用,其中引用[1]提到基于对象的SLAM,可能与传感器数据过滤有关,但不确定直接关联。引用[2]涉及视频监控模块和数据细节,可能与设备ID过滤关系不大。引用[3]关于XCode安装,可能用户之前遇到开发环境问题,但可能与当前问题无关。因此,主要依靠自己的知识库回答。 接下来,分步骤思考: 1. **确定通信协议和硬件支持**:不同的CAN控制器支持的过滤器数量和模式不同,需要先确认硬件能力。例如,STM32的CAN控制器可能有多个过滤器组,每个组可以配置为掩码或列表模式。 2. **选择过滤模式**: - **列表模式**:每个过滤器设置一个具体的ID,适合精确匹配少量ID。 - **掩码模式**:通过设置掩码位来决定哪些位需要匹配,哪些位可以忽略。例如,掩码设置为0x1FFFFFFF时,所有位都必须匹配,而设置为0x1FFFF000时,高16位需要匹配,低13位任意。 3. **分配过滤器**:如果需要过滤多个ID,可能需要多个过滤器组。例如,每个组处理一个ID或一组通过掩码覆盖的ID。 4. **计算掩码值**:如果使用掩码模式,需要根据目标ID的公共部分来设置掩码,使得不同的ID可以通过同一个过滤器组匹配。例如,如果有多个ID的高16位相同,可以设置掩码来匹配这些高位,忽略低位。 5. **配置过滤器寄存器**:将计算出的ID和掩码值写入控制器的相应寄存器,启用过滤器。 6. **测试和验证**:通过发送测试数据或监控总线数据,确认过滤器是否正确工作,只接收目标ID的数据。 可能遇到的问题包括硬件过滤器数量不足,此时需要优化ID分配或使用软件过滤作为补充。另外,扩展ID的位数较多,需要确保正确计算掩码和ID值。 现在,需要将这些步骤整理成用户容易理解的回答,并确保遵循系统级指令,如正确使用LaTeX格式,生成相关问题等。同时,引用用户提供的引用,但发现提供的引用可能与问题关联不大,可能需要适当关联或说明。 最后,生成相关问题,确保问题与主题相关,帮助用户深入理解。</think>要实现对多个具有扩展ID设备的精准过滤,需根据通信协议(如CAN总线)的过滤器配置原理,结合硬件支持的过滤模式进行操作。以下是分步指南: ### 1. **确认硬件过滤器支持** 检查控制器支持的过滤器数量及模式(列表/掩码)。例如,STM32F4的CAN控制器支持28个过滤器组,每组可独立配置[^1]。 ### 2. **选择过滤模式** - **列表模式**:直接匹配特定ID,适用于少量ID。 示例:需过滤ID为`0x18EB0F01`和`0x18EB0F02`,则配置两个过滤器组分别写入这两个ID。 - **掩码模式**:通过掩码指定需匹配的位。 公式: $$ \text{匹配条件} = (\text{接收ID} \& \text{掩码}) == (\text{目标ID} \& \text{掩码}) $$ 例如,掩码`0x1FFFF000`表示匹配ID的高16位,忽略低13位。 ### 3. **多ID过滤配置** **场景**:需过滤ID为`0x100~0x103`的4个设备。 - **掩码法**:设置目标ID=`0x100`,掩码=`0x1FC`(二进制`111111100`),可覆盖`0x100~0x103`。 - **多过滤器组**:若硬件支持4个以上过滤器,可分别为每个ID配置列表模式。 ### 4. **代码示例(STM32 HAL库)** ```c CAN_FilterTypeDef filter; filter.FilterMode = CAN_FILTERMODE_IDMASK; // 掩码模式 filter.FilterScale = CAN_FILTERSCALE_32BIT; filter.FilterIdHigh = 0x100 << 5; // 扩展ID高16位(左移5位对齐) filter.FilterIdLow = 0; filter.FilterMaskIdHigh = 0x1FC << 5; // 掩码高16位 filter.FilterMaskIdLow = 0x0000; filter.FilterFIFOAssignment = CAN_RX_FIFO0; HAL_CAN_ConfigFilter(&hcan, &filter); ``` ### 5. **验证与调试** - 使用CAN分析仪发送测试帧,验证是否仅目标ID被接收。 - 若存在漏过滤,检查掩码计算或尝试列表模式[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值