当前位置: 首页 > ops >正文

【面试场景题】如何快速判断几十亿个数中是否存在某个数

文章目录

      • 一、位图(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. 核心原理
  1. 初始化:创建一个长度为m的位图,初始化所有比特位为0;选择k个独立的哈希函数(如MurmurHash、FNV-1a)。
  2. 插入数据
  • 对数据x,用k个哈希函数计算出k个不同的整数索引h1(x), h2(x), ..., hk(x)
  • 将位图中这k个索引对应的比特位设为1。
  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. 场景1:判断“用户ID是否在活跃用户列表中”(用户ID是0~10^8的整数):
    → 选位图:内存仅12.5MB(10^8/8=12.5MB),100%准确,查询效率O(1)。

  2. 场景2:判断“某URL是否在黑名单中”(URL数量10亿,范围不可控):
    → 选布隆过滤器:内存约500MB(误判率0.001%),先通过布隆过滤器快速过滤“一定不存在”的URL,对“可能存在”的URL再查询数据库确认,避免数据库压力。

  3. 分布式场景(如多服务共享“存在性判断”):
    → 位图:基于Redis的bitfield实现(如Redis的SETBIT/GETBIT命令);
    → 布隆过滤器:基于Redis的bitfield或Redisson的RBloomFilter实现,支持多服务访问。

四、总结

  • 若数据是整数且范围可控,优先用位图:内存最优、100%准确;
  • 若数据类型不限或范围不可控,用布隆过滤器:通过极小误判率换取内存和范围灵活性,配合“双层校验”(布隆过滤器+数据源)可实现100%准确;
  • 二者均能在几十亿数据量下实现“毫秒级查询”,且内存占用远低于传统数据结构,是该场景的最优解。
http://www.xdnf.cn/news/19461.html

相关文章:

  • python-pptx 库(最常用,适合生成/修改 PPT 文件)
  • 深入解析quiche开源项目:从QUIC协议到云原生实践
  • 大模型微调与LoRA/QLoRA方法解析
  • 四、练习1:Git基础操作
  • Python爬虫实战:研究Colormap,构建优质色彩方案数据采集和分析系统
  • 学习:uniapp全栈微信小程序vue3后台-暂时停更
  • C# Task 入门:让你的程序告别卡顿
  • 一文读懂k8s的pv与pvc原理
  • 【Proteus仿真】8*8LED点阵控制系列仿真——循环显示数字/按键控制显示图案
  • 【Netty4核心原理⑭】【Netty 内存分配 ByteBuf❷】
  • 计算机组成原理1 组成与各部件流程 9.1
  • 国内服务器如何安装docker或者是1panel
  • 鸿蒙总改变字体大小设置
  • 计算机网络---https(超文本传输安全协议)
  • Kafka面试精讲 Day 4:Consumer消费者模型与消费组
  • SQLSERVER关键字
  • npm 打包上传命令,撤销错误版本
  • 智能核心:机器人芯片的科技革新与未来挑战
  • 开源npm引导guide组件
  • GIT(了解)
  • 音视频开发入门:FFmpeg vs GStreamer,新手该如何选择?
  • 前端数据可视化:基于Vue3封装 ECharts 的最佳实践
  • Prometheus Alertmanager 告警组件学习
  • GD32F303在移植FreeRTOS时,系统卡死在Systick_Handler B.的处理方法
  • 164.在 Vue3 中使用 OpenLayers 加载 Esri 地图(多种形式)
  • 后端Web实战-多表操作员工列表查询
  • Spring Bean生命周期的完全指南
  • 面试常考css:三列布局实现方式
  • Interceptor拦截器入门知识及其工作原理
  • 虚拟化技术是什么?电脑Bios中的虚拟化技术怎么开启