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 内部:

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

│ long[15625] 数组 │

│ 每个 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
线程安全 ❌ 需外部同步 ❌ 需外部同步
相关推荐
Traving Yu1 小时前
Spring源码与框架原理
java·后端·spring
Lyyaoo.1 小时前
【JAVA基础面经】线程安全的单例模式
java·安全·单例模式
AI服务老曹2 小时前
异构计算与边缘协同:基于 Spring Boot 的 AI 视频管理平台架构深度解析
人工智能·spring boot·音视频
_李小白2 小时前
【OSG学习笔记】Day 39: NodeCallback(帧回调机制)
java·笔记·学习
如来神掌十八式2 小时前
设计模式之装饰器模式
java·设计模式
好大哥呀2 小时前
如何在Spring Boot中配置数据库连接?
数据库·spring boot·后端
cch89182 小时前
C++、Python与汇编语言终极对比
java·开发语言·jvm
老神在在0012 小时前
企业级 SpringBoot 后端通用开发规范|统一响应 + 敏感字段加密
spring boot·后端·状态模式
tsyjjOvO2 小时前
【Spring Data Redis 从入门到实战】一站式掌握 Redis 操作与封装
redis·spring