xml
<dependency>
<groupId>org.roaringbitmap</groupId>
<artifactId>RoaringBitmap</artifactId>
<version>0.9.23</version> <!-- 建议使用最新稳定版本 -->
</dependency>
一、工具类概览
该工具类提供了将List<Long>转换为RoaringBitmap的多种方式,并内置了集合逻辑运算方法。核心目标:让开发者用几行代码完成Long集合的高效存储与运算。
二、方法详解与使用场景
1. eachLongToBitmap / eachLongToBitmapStream
代码作用 :遍历输入列表,为每一个Long值单独创建一个RoaringBitmap,每个bitmap中只包含该值。
java
swift
public static List<RoaringBitmap> eachLongToBitmap(List<Long> longList)
public static List<RoaringBitmap> eachLongToBitmapStream(List<Long> longList) // Stream版本
📌 使用场景
- 文档-词项倒排索引 :假设每个文档ID对应多个关键词,但你需要反向记录每个关键词出现在哪些文档的哪个位置。如果位置信息用独立bitmap存储,可以快速定位"关键词X在第3个位置出现"的所有文档。
- 用户行为序列的事件标记:在点击流分析中,每个用户ID对应一组带时间戳的行为。如果你需要独立追踪每个行为发生的顺序(例如"第1次点击商品A"),可以为每个行为序号创建独立的bitmap,从而高效查询"完成过第5次点击的用户集合"。
- 分布式ID分片校验 :在分布式系统中,有时需要验证一批ID是否属于某个分片。将每个ID单独转为bitmap后,可以与分片的bitmap进行快速成员测试(
contains)。
⚠️ 注意:此方式会生成
N个bitmap对象,内存开销随列表长度线性增长,仅适合列表长度较小(如<1000)或对"每个元素独立操作"有强需求的场景。
2. allLongsToOneBitmap / allLongsToOneBitmapStream
代码作用 :将整个List<Long>中的所有值合并到一个RoaringBitmap中,返回仅包含这一个bitmap的列表。
java
swift
public static List<RoaringBitmap> allLongsToOneBitmap(List<Long> longList)
public static List<RoaringBitmap> allLongsToOneBitmapStream(List<Long> longList) // Stream版本
📌 使用场景(最常见)
- 用户标签系统 :电商平台有"高活跃用户"、"高消费用户"、"偏好电子产品用户"三个标签。每个标签对应一个包含几十万用户ID的
List<Long>,用allLongsToOneBitmap分别生成三个bitmap,然后可以秒级计算"高活跃 ∩ 高消费 ∩ 电子产品"的精准营销人群。 - 黑白名单过滤:风控系统中,黑名单ID集合通常很大(千万级)。将其加载为一个RoaringBitmap,判断一个ID是否在黑名单中仅需O(1)时间,且内存仅为HashSet的1/10左右。
- 离线数据同步时的集合差集 :每日从Hive中导出一批需要推送的用户ID(增量),与昨日已推送的bitmap做
andNot,只处理新增用户。整个过程无需遍历,直接位运算完成。
✅ 推荐:绝大多数"集合存储与运算"场景应优先采用此方式。
3. computeLogic 私有方法
代码作用 :对传入的多个RoaringBitmap执行AND(交集)或OR(并集)运算,返回结果bitmap。
java
arduino
private static RoaringBitmap computeLogic(List<RoaringBitmap> bitmaps, String logic)
📌 使用场景
- 多条件组合筛选 :招聘平台中,职位搜索条件为"学历=本科 AND 经验=3~5年 AND 城市=北京"。每个条件对应一个候选职位ID的bitmap,通过
computeLogic(bitmaps, "AND")直接获得最终职位列表。 - A/B实验流量划分 :实验平台需要将用户分为"对照组"和"实验组"。两组用户分别对应两个bitmap,当需要"实验组且未在对照组中"的用户时,使用
AND结合andNot。而需要"实验组或对照组"的总人群时,使用OR。 - 动态权限聚合 :一个用户属于多个角色,每个角色有一组可访问的资源ID(bitmap)。最终用户的权限 = 各角色资源的
OR;若要实现"必须具备所有角色才能访问"的安全策略,则使用AND。
三、集合运算示例与实际应用场景
main方法中演示了交、并、差、对称差等典型运算。我将为每个运算补充更具象的业务案例。
1. 交集(AND)------ RoaringBitmap.and(bitmapA, bitmapB)
业务场景 :精准营销人群圈选
某APP想给"过去7天登录过"且"在购物车中添加过商品"的用户推送优惠券。
- 集合A:登录用户ID
- 集合B:加购用户ID
交集结果即为目标人群。
2. 并集(OR)------ RoaringBitmap.or(bitmapA, bitmapB)
业务场景 :合并多渠道用户来源
市场部门需要统计"通过抖音广告"或"通过百度搜索"进入过落地页的用户总数(去重)。
- 集合A:抖音渠道用户
- 集合B:百度渠道用户
并集给出独立用户总数。
3. 差集(AND NOT)------ RoaringBitmap.andNot(bitmapA, bitmapB)
业务场景 :排除已推送过的用户
运营每天要发push,但不能重复推送给昨天已收到push的用户。
- 集合A:今天符合条件的用户
- 集合B:昨天已推送的用户
差集A - B得到"今天应推送但昨天未推送"的用户。
4. 对称差(XOR)------ RoaringBitmap.xor(bitmapA, bitmapB)
业务场景 :好友推荐中的"双向未关注"
社交平台:A的好友列表为集合A,B的好友列表为集合B。对称差包含"在A中但不在B中"以及"在B中但不在A中"的人,即双方尚未建立好友关系的候选推荐对象。
5. 多Bitmap逻辑组合(computeLogic)
业务场景 :高级筛选器
电商后台:商家需要筛选出"价格在100~200元" 且 ("品牌为小米" 或 "品牌为华为")的商品。
- 生成三个bitmap:
priceBitmap,xiaomiBitmap,huaweiBitmap - 先用
computeLogic对小米和华为做OR,再与priceBitmap做AND。
四、总结与最佳实践
| 转换方式 | 内存占用 | 适用数据量 | 典型场景 |
|---|---|---|---|
eachLongToBitmap |
高(N个bitmap对象) | 小(N<1000) | 位置索引、独立事件标记 |
allLongsToOneBitmap |
低(1个bitmap) | 极大(千万级) | 用户画像、黑白名单、集合运算 |
额外建议:
- 若原始
Long值可能超过int范围(> 2^31-1),RoaringBitmap无法直接存储。实际业务中用户ID、商品ID通常控制在int范围内,否则需采用Roaring64NavigableMap。 - 对于Stream版本,小列表可提升可读性,大列表推荐使用传统循环以避免装箱开销。
- 集合运算尽量使用RoaringBitmap提供的静态方法(如
and,or,xor,andNot),它们内部经过高度优化,比手动循环快数倍。
通过合理运用RoaringBitmap,你可以在不引入重型计算引擎(如Spark、Flink)的情况下,在单机内存中轻松处理亿级ID的集合运算。希望这篇AI生成的文章能帮助你在实际项目中选对工具、用对场景。
java 代码 示例
ini
public class LongListToBitmapList {
/**
* 方式一:每个 Long 值生成一个独立的 RoaringBitmap
* @param longList 原始 Long 列表
* @return List<RoaringBitmap> 每个 bitmap 包含对应位置的一个 long 值
*/
public static List<RoaringBitmap> eachLongToBitmap(List<Long> longList) {
List<RoaringBitmap> bitmapList = new ArrayList<>();
for (Long value : longList) {
RoaringBitmap bitmap = new RoaringBitmap();
// RoaringBitmap 存储的是 int 范围内的无符号整数,long 需要转成 int(实际场景确保值不超过 int 范围)
bitmap.add(value.intValue());
bitmapList.add(bitmap);
}
return bitmapList;
}
/**
* 方式二:将所有 Long 值合并到一个 RoaringBitmap 中,然后返回仅含该 bitmap 的列表
* @param longList 原始 Long 列表
* @return List<RoaringBitmap> 包含一个聚合了所有值的 bitmap
*/
public static List<RoaringBitmap> allLongsToOneBitmap(List<Long> longList) {
RoaringBitmap bitmap = new RoaringBitmap();
for (Long value : longList) {
bitmap.add(value.intValue());
}
List<RoaringBitmap> result = new ArrayList<>();
result.add(bitmap);
return result;
}
// 使用 Java Stream 的简洁写法(方式一)
public static List<RoaringBitmap> eachLongToBitmapStream(List<Long> longList) {
return longList.stream()
.map(value -> {
RoaringBitmap bitmap = new RoaringBitmap();
bitmap.add(value.intValue());
return bitmap;
})
.collect(Collectors.toList());
}
// 使用 Java Stream 的简洁写法(方式二)
public static List<RoaringBitmap> allLongsToOneBitmapStream(List<Long> longList) {
RoaringBitmap bitmap = new RoaringBitmap();
longList.forEach(value -> bitmap.add(value.intValue()));
return Arrays.asList(bitmap);
}
/**
* 对多个 RoaringBitmap 执行交(AND)或并(OR)运算
* @param bitmaps 参与运算的 bitmap 列表
* @param logic "AND" 或 "OR"(大小写不敏感)
* @return 运算结果
*/
private static RoaringBitmap computeLogic(List<RoaringBitmap> bitmaps, String logic) {
if (bitmaps == null || bitmaps.isEmpty()) {
return new RoaringBitmap();
}
RoaringBitmap result = new RoaringBitmap();
if ("AND".equalsIgnoreCase(logic)) {
// 先用第一个 bitmap 作为初始值,再与其余做 and
result.or(bitmaps.get(0));
for (int i = 1; i < bitmaps.size(); i++) {
result.and(bitmaps.get(i));
}
} else if ("OR".equalsIgnoreCase(logic)) {
bitmaps.forEach(result::or);
}
return result;
}
// 测试示例(包含交、并、差)
public static void main(String[] args) {
// 准备两个 Long 列表,分别代表两个集合
List<Long> listA = Arrays.asList(1L, 2L, 3L, 4L, 5L);
List<Long> listB = Arrays.asList(4L, 5L, 6L, 7L, 8L);
// 转换为独立的 bitmap(每个元素一个 bitmap,用于演示多 bitmap 交并)
// 实际业务中往往每个集合对应一个 bitmap(包含多个元素),这里我们演示两种风格:
// 风格1:每个集合合并为一个 bitmap(更常见)
RoaringBitmap bitmapA = new RoaringBitmap();
listA.forEach(v -> bitmapA.add(v.intValue()));
RoaringBitmap bitmapB = new RoaringBitmap();
listB.forEach(v -> bitmapB.add(v.intValue()));
System.out.println("集合 A: " + listA);
System.out.println("集合 B: " + listB);
// 1. 交集 (AND)
RoaringBitmap intersection = RoaringBitmap.and(bitmapA, bitmapB);
System.out.println("交集 (A ∩ B): " + intersection);
// 2. 并集 (OR)
RoaringBitmap union = RoaringBitmap.or(bitmapA, bitmapB);
System.out.println("并集 (A ∪ B): " + union);
// 3. 差集 (A - B,即 A AND NOT B)
RoaringBitmap differenceAB = RoaringBitmap.andNot(bitmapA, bitmapB);
System.out.println("差集 (A - B): " + differenceAB);
// 4. 差集 (B - A)
RoaringBitmap differenceBA = RoaringBitmap.andNot(bitmapB, bitmapA);
System.out.println("差集 (B - A): " + differenceBA);
// 5. 对称差 (XOR) 即 (A - B) ∪ (B - A)
RoaringBitmap xor = RoaringBitmap.xor(bitmapA, bitmapB);
System.out.println("对称差 (A ⊕ B): " + xor);
// 6. 演示 computeLogic 方法(支持多个 bitmap)
List<RoaringBitmap> multiBitmaps = Arrays.asList(bitmapA, bitmapB);
RoaringBitmap andResult = computeLogic(multiBitmaps, "AND");
RoaringBitmap orResult = computeLogic(multiBitmaps, "OR");
System.out.println("computeLogic AND: " + andResult);
System.out.println("computeLogic OR : " + orResult);
}
-------------结果如下-------------
集合 A: [1, 2, 3, 4, 5]
集合 B: [4, 5, 6, 7, 8]
交集 (A ∩ B): {4,5}
并集 (A ∪ B): {1,2,3,4,5,6,7,8}
差集 (A - B): {1,2,3}
差集 (B - A): {6,7,8}
对称差 (A ⊕ B): {1,2,3,6,7,8}
computeLogic AND: {4,5}
computeLogic OR : {1,2,3,4,5,6,7,8}