【面试场景题】如何快速判断几十亿个数中是否存在某个数
文章目录
- 一、位图(BitMap):适用于“整数型、范围可控”的场景
- 1. 适用条件
- 2. 核心原理
- 3. 实现步骤(以Java为例)
- 4. 优缺点
- 二、布隆过滤器(Bloom Filter):适用于“类型不限、范围不可控”的场景
- 1. 适用条件
- 2. 核心原理
- 3. 实现步骤(以Guava布隆过滤器为例)
- 4. 优缺点
- 三、选型对比与最佳实践
- 最佳实践
- 四、总结
在几十亿个数中快速判断“某个数是否存在”,核心需求是极致的查询效率(O(1)) 和极低的内存占用——传统数据库或哈希表会因数据量过大导致内存溢出,而布隆过滤器(Bloom Filter) 和位图(BitMap) 是专为这类场景设计的高效数据结构,二者各有适用场景,以下是具体实现方案和选型建议:
一、位图(BitMap):适用于“整数型、范围可控”的场景
位图的核心思想是用1个比特(Bit)表示1个数字的存在状态(0=不存在,1=存在),通过“比特级存储”实现内存极致压缩,查询效率达O(1)。
1. 适用条件
- 待判断的数是整数类型(如ID、手机号、订单号等,可转为整数)。
- 数的范围可控(如已知所有数在0~10^10之间,可预估位图大小)。
若数的范围过大(如10^18),位图会因“稀疏存储”导致内存浪费,此时更适合布隆过滤器。
2. 核心原理
- 例如:判断“100是否存在”,只需将位图的第100个比特位设为1;查询时直接读取第100个比特位,若为1则存在,为0则不存在。
- 内存计算:若数的范围是0~N-1,位图需占用 N/8 字节(1字节=8比特)。
示例:存储10亿个整数(范围0~10^9-1),位图仅需 10^9 / 8 = 125MB 内存,远低于哈希表(存储10亿个int需4GB)。
3. 实现步骤(以Java为例)
Java中可通过
java.util.BitSet
(底层是long数组,每个long存储64个比特)实现位图,无需手动管理比特位:
import java.util.BitSet;public class BitMapExample {public static void main(String[] args) {// 1. 初始化位图:假设数的范围是0~10^9-1(10亿个数)BitSet bitMap = new BitSet(1000000000);// 2. 插入数据:将存在的数对应的比特位设为1long[] existNumbers = {123, 456, 789, 1000000, ...}; // 几十亿个数的子集for (long num : existNumbers) {bitMap.set((int) num); // 注意:num需在BitSet初始化的范围内,否则会自动扩容}// 3. 查询数据:判断数是否存在long target = 456;if (bitMap.get((int) target)) {System.out.println(target + " 存在");} else {System.out.println(target + " 不存在");}}
}
4. 优缺点
- 优点:
- 内存占用极低(比特级存储);
- 查询/插入效率O(1),无误差(100%准确);
- 支持批量操作(如
and
/or
,可快速求两个集合的交集/并集)。
- 缺点:
- 仅支持整数类型(非整数需先哈希转为整数,可能冲突);
- 数的范围过大时内存浪费(如存储10^12的数,需125GB内存,不现实)。
二、布隆过滤器(Bloom Filter):适用于“类型不限、范围不可控”的场景
布隆过滤器是位图的“升级版”,通过多个哈希函数+位图实现“允许极小误判率(假阳性)”的存在性判断,支持任意类型数据,内存占用仅与“数据量”和“误判率”相关,与数据本身范围无关。
1. 适用条件
- 待判断的数类型不限(整数、字符串、二进制数据等,均可通过哈希转为整数)。
- 数的范围不可控(如手机号、UUID、URL等,无法预估最大范围)。
- 业务可接受极低的假阳性(False Positive)(即“判断为存在,但实际不存在”,但绝不会出现“假阴性”——判断为不存在,则一定不存在)。
2. 核心原理
- 初始化:创建一个长度为
m
的位图,初始化所有比特位为0;选择k
个独立的哈希函数(如MurmurHash、FNV-1a)。- 插入数据:
- 对数据
x
,用k
个哈希函数计算出k
个不同的整数索引h1(x), h2(x), ..., hk(x)
;- 将位图中这
k
个索引对应的比特位设为1。
- 查询数据:
- 对目标数据
x
,同样计算k
个索引;- 若所有索引对应的比特位均为1 → 数据“可能存在”(存在假阳性);
- 若任一索引对应的比特位为0 → 数据“一定不存在”(无假阴性)。
- 内存与误判率计算:
给定数据量
n
和期望误判率p
,位图长度m
和哈希函数个数k
的最优解为:
( m = -\frac{n \times \ln p}{(\ln 2)^2} ),( k = \frac{m}{n} \times \ln 2 )
示例:存储10亿个数,期望误判率0.001%(1e-6):
- 需
m ≈ 40
亿比特(约500MB),k=28
个哈希函数;- 内存仅500MB,远低于哈希表(4GB),且与数据范围无关。
3. 实现步骤(以Guava布隆过滤器为例)
Java中可直接使用Google Guava的
BloomFilter
(无需手动实现哈希函数和位图),也可基于Redis的bitfield
实现分布式布隆过滤器:
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;public class BloomFilterExample {public static void main(String[] args) {// 1. 初始化布隆过滤器:// - Funnels.longFunnel():数据类型为long(若为字符串用Funnels.stringFunnel(Charset.defaultCharset()))// - expectedInsertions:预计插入数据量(10亿)// - fpp:期望误判率(0.001%)BloomFilter<Long> bloomFilter = BloomFilter.create(Funnels.longFunnel(),1000000000L,0.00001);// 2. 插入数据(几十亿个数的子集)long[] existNumbers = {123, 456, 789, 1000000, ...};for (long num : existNumbers) {bloomFilter.put(num);}// 3. 查询数据long target1 = 456;if (bloomFilter.mightContain(target1)) {System.out.println(target1 + " 可能存在(需进一步验证,避免假阳性)");// 若需100%准确,可再查询数据库/Redis确认(如“布隆过滤器+缓存”双层校验)} else {System.out.println(target1 + " 一定不存在(无需后续操作)");}long target2 = 999999;if (!bloomFilter.mightContain(target2)) {System.out.println(target2 + " 一定不存在");}}
}
4. 优缺点
- 优点:
- 内存占用极低(仅与数据量和误判率相关);
- 支持任意类型数据(通过哈希转换);
- 查询/插入效率O(k)(k为哈希函数个数,通常20~30,接近O(1));
- 可分布式实现(如基于Redis的bitfield,支持集群环境)。
- 缺点:
- 存在假阳性(需业务可接受,或通过“布隆过滤器+数据源”双层校验解决);
- 不支持删除操作(删除一个比特位会影响其他数据,若需删除可使用“计数布隆过滤器”,但内存占用翻倍)。
三、选型对比与最佳实践
对比维度 | 位图(BitMap) | 布隆过滤器(Bloom Filter) |
---|---|---|
数据类型 | 仅支持整数(需范围可控) | 支持任意类型(整数、字符串等) |
内存占用 | 与数据范围相关(范围越小越优) | 与数据量、误判率相关(范围无关) |
准确性 | 100%准确(无误差) | 无假阴性,有极低假阳性 |
支持操作 | 插入、查询、交集/并集 | 插入、查询(基本版不支持删除) |
适用场景 | 整数ID、固定范围数据(如用户ID) | 范围不可控数据(如手机号、URL) |
最佳实践
场景1:判断“用户ID是否在活跃用户列表中”(用户ID是0~10^8的整数):
→ 选位图:内存仅12.5MB(10^8/8=12.5MB),100%准确,查询效率O(1)。场景2:判断“某URL是否在黑名单中”(URL数量10亿,范围不可控):
→ 选布隆过滤器:内存约500MB(误判率0.001%),先通过布隆过滤器快速过滤“一定不存在”的URL,对“可能存在”的URL再查询数据库确认,避免数据库压力。分布式场景(如多服务共享“存在性判断”):
→ 位图:基于Redis的bitfield
实现(如Redis的SETBIT
/GETBIT
命令);
→ 布隆过滤器:基于Redis的bitfield
或Redisson的RBloomFilter
实现,支持多服务访问。
四、总结
- 若数据是整数且范围可控,优先用位图:内存最优、100%准确;
- 若数据类型不限或范围不可控,用布隆过滤器:通过极小误判率换取内存和范围灵活性,配合“双层校验”(布隆过滤器+数据源)可实现100%准确;
- 二者均能在几十亿数据量下实现“毫秒级查询”,且内存占用远低于传统数据结构,是该场景的最优解。