RoaringBitmap在SpringBoot中的使用以及与BitSet对比

最近在程序化中使用到了位图,但是我们的系统使用的是java util下的BitSet,我们是把素材还有人员id等先存储到一个Map中,然后BitSet中存储对应的下标,先获取到对应的下标,然后利用BitSet去求交集和并集,所以就自己研究了下更高性能的一个位图RoaringBitmap。RoaringBitmap 是一个高性能的压缩位图库,是 Java 中BitSet 的升级版。

直接上代码,首先需要引入对应的maven库

复制代码
<dependency>
    <groupId>org.roaringbitmap</groupId>
    <artifactId>RoaringBitmap</artifactId>
    <version>1.2.3</version>
</dependency>

import org.roaringbitmap.RoaringBitmap;

public class RoaringDemo {
    public static void main(String[] args) {
        // 创建空位图
        RoaringBitmap rb = new RoaringBitmap();
        
        // 添加单个元素(0 <= value < 2^32)
        rb.add(1);
        rb.add(100);
        rb.add(100000);
        
        // 批量添加
        rb.add(1L, 1000L);        // 添加 [1, 1000) 区间
        rb.add(0, 1, 2, 3, 4);    // 变参添加
        
        // 检查存在性
        boolean contains = rb.contains(100);  // true
        
        // 删除元素
        rb.remove(100);
        rb.remove(1L, 100L);      // 删除区间
        
        // 获取统计信息
        System.out.println(rb.getCardinality());  // 元素个数
        System.out.println(rb.getSizeInBytes());    // 实际占用字节数
    }
}

布尔集合运算(交集/并集)

复制代码
RoaringBitmap userA = new RoaringBitmap();
userA.add(1, 2, 3, 4, 5);

RoaringBitmap userB = new RoaringBitmap();
userB.add(4, 5, 6, 7, 8);

// 并集 (OR) - A 或 B 喜欢的
RoaringBitmap union = RoaringBitmap.or(userA, userB);
// 结果: {1,2,3,4,5,6,7,8}

// 交集 (AND) - A 和 B 都喜欢的
RoaringBitmap intersection = RoaringBitmap.and(userA, userB);
// 结果: {4,5}

// 差集 (ANDNOT) - A 喜欢但 B 不喜欢的
RoaringBitmap diff = RoaringBitmap.andNot(userA, userB);
// 结果: {1,2,3}

// 异或 (XOR) - 只被一个人喜欢的
RoaringBitmap xor = RoaringBitmap.xor(userA, userB);
// 结果: {1,2,3,6,7,8}

// 原地修改版本(更省内存)
userA.or(userB);      // userA 变为并集
userA.and(userB);     // userA 变为交集

范围查询与切片

复制代码
RoaringBitmap rb = new RoaringBitmap();
rb.add(0L, 1000000L);  // 100 万个连续数字

// 获取范围切片(不拷贝数据,视图操作)
RoaringBitmap subset = rb.selectRange(100, 200);  // [100, 200)

// 限制最大返回数量
RoaringBitmap limited = rb.limit(100);  // 前 100 个元素

// 排名查询
int rank = rb.rank(500);  // 小于 500 的元素个数

// 选择第 N 个元素
int value = rb.select(99);  // 第 100 小的元素(0-based)

序列化与反序列化

复制代码
// 序列化到文件(紧凑格式)
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("bitmap.bin"))) {
    rb.serialize(dos);
}

// 反序列化
try (DataInputStream dis = new DataInputStream(new FileInputStream("bitmap.bin"))) {
    RoaringBitmap rb2 = new RoaringBitmap();
    rb2.deserialize(dis);
}

// 获取字节数组(网络传输)
byte[] bytes = new byte[rb.serializedSizeInBytes()];
rb.serialize(ByteBuffer.wrap(bytes));

并行批量操作

复制代码
// 并行 OR(多核加速)
RoaringBitmap[] bitmaps = {rb1, rb2, rb3, rb4, rb5};
RoaringBitmap result = RoaringBitmap.parOr(bitmaps);

// 并行 AND
RoaringBitmap result2 = RoaringBitmap.parAnd(bitmaps);

// 并行聚合统计
long[] cardinalities = RoaringBitmap.orCardinality(bitmaps);

用户画像标签系统中的使用

复制代码
public class UserTagSystem {
    // 标签 -> 用户集合(将拥有该标签的用户统一放在一个RoaringBitmap中)
    private Map<String, RoaringBitmap> tagIndex = new HashMap<>();
    
    // 给用户打标签
    public void tagUser(String tag, int userId) {
        tagIndex.computeIfAbsent(tag, k -> new RoaringBitmap()).add(userId);
    }
    
    // 查找同时具有多个标签的用户(交集)
    public RoaringBitmap findUsersWithAllTags(String... tags) {
        if (tags.length == 0) return new RoaringBitmap();
        
        RoaringBitmap result = tagIndex.getOrDefault(tags[0], new RoaringBitmap()).clone();
        for (int i = 1; i < tags.length; i++) {
            result.and(tagIndex.getOrDefault(tags[i], new RoaringBitmap()));
        }
        return result;
    }
    
    // 查找具有任意一个标签的用户(并集)
    public RoaringBitmap findUsersWithAnyTag(String... tags) {
        RoaringBitmap[] bitmaps = Arrays.stream(tags)
            .map(t -> tagIndex.getOrDefault(t, new RoaringBitmap()))
            .toArray(RoaringBitmap[]::new);
        return RoaringBitmap.or(bitmaps);
    }
    
    // 获取用户数量
    public int getUserCount(String tag) {
        return tagIndex.getOrDefault(tag, new RoaringBitmap()).getCardinality();
    }
}

RoaringBitmap 是处理海量整数集合的高性能压缩位图 ,相比 BitSet 节省 10-100 倍内存,集合运算快数倍,是大数据场景的标配工具。

与BitSet对比

基础操作对比:

复制代码
// ========== BitSet ==========
BitSet bs = new BitSet();
bs.set(100);
bs.set(1000000);
System.out.println(bs.get(100));      // true
System.out.println(bs.cardinality()); // 2

// 遍历(低效,遍历所有位)
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
    System.out.println(i);
}

// 集合运算(手动实现,极慢)
BitSet andResult = (BitSet) bs1.clone();
andResult.and(bs2);


// ========== RoaringBitmap ==========
RoaringBitmap rb = new RoaringBitmap();
rb.add(100);
rb.add(1000000);
System.out.println(rb.contains(100));  // true
System.out.println(rb.getCardinality()); // 2

// 遍历(高效,只遍历存在的)
for (int value : rb) {
    System.out.println(value);
}

// 集合运算(原生优化,极快)
RoaringBitmap andResult = RoaringBitmap.and(rb1, rb2);
RoaringBitmap orResult = RoaringBitmap.or(rb1, rb2);

两个特性对比:

特性 BitSet RoaringBitmap
定位 Java 标准库基础位图 高性能压缩位图库
设计目标 通用、简单 海量数据、高性能
数据特点 适合密集数据 适合稀疏数据
内存策略 固定分配,不压缩 自适应压缩,动态调整

存储机制对比

数据:5, 6, 7, 1000000

BitSet 内部:

┌────────────────────────────────────────┐

│ long15625 数组 │

│ 每个 long = 64 bit │

│ 要存 1000000,必须分配 1000001 bit │

│ │

5=1 6=1 7=1 ... 1000000=1 │

│ ↓ ↓ ↓ ↓ │

│ 大量空间浪费(中间都是0) │

└────────────────────────────────────────┘

内存:1000001 bit ≈ 122 KB(无论实际有几个1)

数据:5, 6, 7, 1000000

RoaringBitmap 内部:

┌─────────────────────────────────────────┐

│ 按高 16 位分区(Chunk),每个 Chunk 65536 │

├─────────────────────────────────────────┤

│ Chunk 0 (0-65535): │

│ ├─ Container 类型: ArrayContainer │

│ └─ 内容: 5, 6, 7(有序数组,3×2字节) │

├─────────────────────────────────────────┤

│ Chunk 15 (983040-1048575): │

│ ├─ Container 类型: ArrayContainer │

│ └─ 内容: 1000000-983040=15936

└─────────────────────────────────────────┘

内存:约 20 字节(稀疏时极省)

关键差异对比:

对比项 BitSet RoaringBitmap
稀疏数据内存 ❌ 差(必须分配最高位) 极优(只存实际数据)
密集数据内存 ✅ 紧凑 ✅ 同样紧凑
添加元素速度 ✅ O(1) 极快 ⚡ O(1) 快(略慢于 BitSet)
遍历速度 ✅ 快 更快(缓存友好)
集合运算 ❌ 需遍历全部位 原生优化,快数倍
范围查询 ❌ 需遍历 原生支持
序列化大小 ❌ 大(全量) (压缩格式)
64位支持 ❌ 不支持(仅 int) Roaring64Bitmap
不可变版本 ❌ 无 ImmutableRoaringBitmap
线程安全 ❌ 需外部同步 ❌ 需外部同步
相关推荐
咩咩啃树皮7 小时前
第40篇:Vue3组件化开发精讲——组件拆分、复用、父子通信、工程化架构
java·前端·架构
鱟鲥鳚8 小时前
Spring Boot 集成 LangChain4j:从模型调用到 Tool Calling(Demo版)
java·spring boot
大模型码小白9 小时前
【Python零基础教程】继承、多态与魔法函数:面向对象编程三大核心特性详解
java·大数据·开发语言·人工智能·python·ai编程
腾渊信息科技公司10 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
爱笑的源码基地11 小时前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
wuqingshun31415911 小时前
TCP超时重传机制是为了解决什么问题?
java
莫逸风13 小时前
【AgentScope 2.0】 0. 学习指南
java·llm·agent·agentscope
隔窗听雨眠13 小时前
Spring Boot在云原生时代的编程范式革新研究
spring boot·后端·云原生
z1234567898614 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
yaoxin52112314 小时前
470. Java 反射 - Member 接口与 AccessFlag
java·开发语言·python