如何使用Java Stream将分组Map的大列表拆分为单key最多N元素的子分组
实现前提
Foo类已正确重写equals()和hashCode()方法,支持作为HashMap的键使用- 你可以基于已有的基础Foo对象(分片序号为0的版本)生成指定分片序号的新Foo实例,比如提供
withSeq(int seq)拷贝方法,或手动调用构造方法传入前三个属性+新序号 - 处理千万级数据前请确认JVM堆内存配置足够承载所有Bar对象,避免OOM
最优实现方案(适配大数据量,性能稳定)
该方案不会额外复制列表元素,内存占用低,执行效率高,适合千万级数据场景:
// 定义单分组最大元素数,你的场景设为2000即可 int MAX_PER_GROUP = 2000; Map<Foo, List<Bar>> baz = new HashMap<>(); // 第一步:先按原始规则(csv第一列等字段)完成首次分组 Map<Foo, List<Bar>> firstGroup = listBar.stream().distinct() .collect(Collectors.groupingBy(b -> b.getFooFromBar(fileAbsolutePath))); // 第二步:对每个分组按大小拆分,生成最终结果 firstGroup.forEach((baseFoo, barList) -> { int totalSize = barList.size(); // 向上取整计算需要拆分的分片总数 int batchNum = (totalSize + MAX_PER_GROUP - 1) / MAX_PER_GROUP; IntStream.range(0, batchNum).forEach(seq -> { int startIdx = seq * MAX_PER_GROUP; int endIdx = Math.min(startIdx + MAX_PER_GROUP, totalSize); // 生成带当前分片序号的Foo键 Foo currentKey = baseFoo.withSeq(seq); // subList返回原列表的视图,不会复制元素,性能极高 baz.put(currentKey, barList.subList(startIdx, endIdx)); }); });
单Stream流实现方案(代码更简洁)
如果偏好单次Stream操作完成,可以用以下写法,注意禁止使用并行流,否则计数会出现混乱:
int MAX_PER_GROUP = 2000; // 存储每个基础分组的元素计数 Map<Foo, AtomicInteger> groupCounter = new ConcurrentHashMap<>(); Map<Foo, List<Bar>> baz = listBar.stream().distinct() .collect(Collectors.groupingBy(bar -> { Foo baseFoo = bar.getFooFromBar(fileAbsolutePath); AtomicInteger counter = groupCounter.computeIfAbsent(baseFoo, k -> new AtomicInteger(0)); // 计算当前元素所属的分片序号 int seq = counter.getAndIncrement() / MAX_PER_GROUP; return baseFoo.withSeq(seq); }));
注意事项
- 如果你的Bar列表没有按csv行顺序排列,请先按行号排序后再执行分组逻辑,否则分片顺序不符合预期
- 如果后续需要修改分片后的列表内容,建议将
subList的结果包装为新的ArrayList:new ArrayList<>(barList.subList(startIdx, endIdx)),避免影响原列表
内容的提问来源于stack exchange,提问作者FinnStark
相关产品推荐
相关产品推荐

