📋 目录
一、工具类功能
用极小的内存判断元素「一定不存在」还是「可能存在」,按预期插入量和目标误判率自动定位数组长度与哈希个数,2 万个元素按 1% 误判率只要 23 KB。
二、所需环境
JDK 17或以上
三、方法与调用示例(按需调用)
🔢 方法序号:1
🎯 方法用途:
- 由预期插入数与目标误判率推导位数组要多长,先算再建实例
- 评估一个布隆过滤器要吃多少内存时用,容量与误判率是这一对参数定的
🧩 方法定义:public static long optimalBitSize(long expectedInsertions, double fpp)
🗣️ 入参解释:public static long optimalBitSize(预期插入数, 目标误判率)
📤 返回值解释:long,需要的位数
💡 边界口径:
- 单个实例最大
2^31-1位(约 256MB),超出直接拒绝,要更大容量按业务维度分片建多个实例 fpp必须落在开区间(0, 1),expectedInsertions必须大于 0,违反抛IllegalArgumentException- 别指望放大
expectedInsertions自动扩容,那只会把误判率静默抬高 - 结果是公式值,不是实测占用字节
🧪 方法示例:
optimalBitSize(20_000, 0.01)拿到191702,约合 23 KBoptimalBitSize(1_000_000_000L, 1e-9)抛所需bit数43132762699超出单个实例上限2147483647位(约256MB),请分片或使用多实例optimalBitSize(1000, 1.0)抛误判率必须在(0,1)之间,当前为1.0optimalBitSize(0, 0.01)抛预期插入数必须大于0,当前为0
🔢 方法序号:2
🎯 方法用途:
- 由位数与预期插入数推导每个元素要设置几个哈希位置
- 与位数组长度配套,哈希个数太少误判率高、太多则白算
🧩 方法定义:public static int optimalHashCount(long bitSize, long expectedInsertions)
🗣️ 入参解释:public static int optimalHashCount(bit位数, 预期插入数)
📤 返回值解释:int,每个元素的哈希位置个数
💡 边界口径:
- 位数与预期插入数都必须大于 0,否则抛
IllegalArgumentException - 这里算出的个数会被
create用进双重哈希,h(i) = h1 + i × h2 - 结果至少为 1,不会出现一个哈希位都不设的过滤器
🧪 方法示例:
optimalHashCount(191702, 20_000)拿到7 个哈希位置optimalHashCount(0, 10)抛bit位数必须大于0,当前为0optimalHashCount(191702, 0)抛预期插入数必须大于0,当前为0
🔢 方法序号:3
🎯 方法用途:
- 按当前装载量算理论误判率,用来看过滤器还在不在可用区间
- 插入量超预期后误判率会明显爬升,这个数是趋势观测的入口
🧩 方法定义:public static double expectedFpp(long bitSize, int hashCount, long inserted)
🗣️ 入参解释:public static double expectedFpp(bit位数, 哈希个数, 已插入数)
📤 返回值解释:double,理论误判率,0 到 1 之间
💡 边界口径:
- 一条都没灌时给
0.0,装载越多越接近 1 inserted取的是insertedCount(),重复添加也计入,所以这是乐观估计- 位数或哈希个数不合法抛
IllegalArgumentException - 是公式值不是实测值,实测要拿不存在的 key 去探测
🧪 方法示例:
expectedFpp(191702, 7, 0)拿到0.0,刚建好时没有误判- 灌到 2 万条时拿到
0.010039010484203732,与设定的 0.01 对得上 - 灌到 4 倍量之后抬到
0.6786804119833408 - 位数被灌满时
expectedFpp(191702, 7, 191702)拿到0.9936342618294455
🔢 方法序号:4
🎯 方法用途:
- 按推导出的位数与哈希个数建一个过滤器实例
- 日常用这一版就够,参数只给预期插入数和目标误判率
🧩 方法定义:public static BloomFilter create(long expectedInsertions, double fpp)
🗣️ 入参解释:public static BloomFilter create(预期插入数, 目标误判率)
📤 返回值解释:BloomFilter,可增可查的实例,非线程安全
💡 边界口径:
- 位数组与哈希个数都由前两个方法推导,不接受手工指定
- 实例本身内部是无锁
BitSet与计数,多线程共享要外部加锁或分片 - 建好时填充率为 0,容量与
expectedInsertions一致 - 参数非法与超上限的报错口径同
optimalBitSize
🧪 方法示例:
create(20_000, 0.01).bitFillRatio()拿到0.0,还没灌数据create(1_000_000_000L, 1e-9)抛所需bit数43132762699超出单个实例上限2147483647位(约256MB),请分片或使用多实例
🔢 方法序号:5
🎯 方法用途:
- 把字符串元素按 UTF-8 编码后放进过滤器
- 订单号、手机号、URL 这类文本判重场景的写入口
🧩 方法定义:public void add(String value)
🗣️ 入参解释:public void add(字符串元素)
📤 返回值解释:void,写入后不返回什么,查询走 mightContain
💡 边界口径:
- 判重按字节走,
"中文"与它的转义字面量是两个不同元素 - 传 null 抛
NullPointerException,避免所有槽位塌陷到 0 号位污染过滤器 - 每次调用都让
insertedCount加一,重复元素也计
🧪 方法示例:
- 灌完 2 万条字符串之后
insertedCount()拿到20001,多的一条来自字节入口 - 加过的 key 用
mightContain("ORD1000001")查得到,拿到true(不可能漏) add(null)抛字符串不能为null
🔢 方法序号:6
🎯 方法用途:
- 字节数组入口写入,与字符串入口同一套哈希与槽位
- 已经是摘要、ID 字节或不想再编码一次的场合用它
🧩 方法定义:public void add(byte[] value)
🗣️ 入参解释:public void add(字节数组元素)
📤 返回值解释:void,写入即生效
💡 边界口径:
- 同一内容的字节与字符串入口互通,字节灌进去的字符串入口查得到
- 传 null 抛
NullPointerException,消息是元素字节不能为null - 空数组是合法元素,会占用一组槽位
🧪 方法示例:
- 字节入口灌进去的,
mightContain用字符串查拿到true mightContain("ORD1000001".getBytes(StandardCharsets.UTF_8))拿到true,两个入口口径一致add((byte[]) null)抛元素字节不能为null
🔢 方法序号:7
🎯 方法用途:
- 查字符串元素是否可能存在,返回 false 是「一定不存在」
- 缓存穿透拦截、海量数据预筛这类只要求「别把没有的放过去」的场景用它
🧩 方法定义:public boolean mightContain(String value)
🗣️ 入参解释:public boolean mightContain(字符串元素)
📤 返回值解释:boolean,false 一定不存在,true 只是可能存在
💡 边界口径:
true不能当权威结论,后面还要回源确认- 装载越满误判越多,配合
bitFillRatio与expectedFpp观察 - 按 UTF-8 判重,转义字面量与原字符不是同一个元素
- 传 null 抛
NullPointerException
🧪 方法示例:
- 加过的 key
mightContain(added)拿到true,不可能漏 - 没加过的 key
mightContain(missing)拿到false,一定不存在可以放心拦截 - 转义字面量
mightContain("\u4E2D\u6587")拿到false,它不是同一个元素 - 灌到 4 倍量之后,同样 2 万次不存在 key 的探测命中数拿到
从 200 涨到 13435,过滤器已经不再省钱 mightContain((String) null)抛字符串不能为null
🔢 方法序号:8
🎯 方法用途:
- 字节形态的查询入口,结论与字符串版一致
- 上游给的是原始字节(摘要、协议体)时用这一版
🧩 方法定义:public boolean mightContain(byte[] value)
🗣️ 入参解释:public boolean mightContain(字节数组元素)
📤 返回值解释:boolean,false 一定不存在,true 只是可能存在
💡 边界口径:
- 与字符串入口共用哈希与位数组,同一字节内容两边结论相同
- 传 null 抛
NullPointerException - 判的是位是否被置起,不看是谁置的,这就是误判来源
🧪 方法示例:
- 没写过的字节数组
mightContain(missing.getBytes(StandardCharsets.UTF_8))拿到false - 写过的
mightContain("SKU1005000".getBytes(StandardCharsets.UTF_8))拿到true,与字符串入口结论一致 - 与
mightContain(String)对同一份内容比对,拿到true
🔢 方法序号:9
🎯 方法用途:
- 读
add的调用次数,用来喂理论误判率公式 - 监控装载量、判断是否已经超出预期插入数时看它
🧩 方法定义:public long insertedCount()
🗣️ 入参解释:public long insertedCount()(无入参)
📤 返回值解释:long,累计 add 调用次数
💡 边界口径:
- 重复添加同样计数,它不是去重后的元素个数
- 只做乐观估计,理论误判率会因此偏低
- 计数只增不减,本类没有删除元素的入口
🧪 方法示例:
- 灌到 4 倍量之后
insertedCount()拿到80001 - 同一元素
add两次后拿到2,重复添加照计
🔢 方法序号:10
🎯 方法用途:
- 读位数组的填充率,越接近 1 说明越失控
- 判断「该重建或分片了」比看插入数更直接,用它
🧩 方法定义:public double bitFillRatio()
🗣️ 入参解释:public double bitFillRatio()(无入参)
📤 返回值解释:double,已置起的位占总位数的比例
💡 边界口径:
- 刚建好是 0.0,灌满接近 1.0,此时任何 key 都会被判「可能存在」
- 是实测的位占用比例,与
expectedFpp的理论值互补 - 读它不改动过滤器状态,可安全用于监控埋点
🧪 方法示例:
- 按 2 万条设计的过滤器灌到 4 倍量后
bitFillRatio()拿到0.9454309292547809 - 新建实例
create(100, 0.03).bitFillRatio()拿到0.0
四、工具类源码(可直接复制)
📦 源码文件