本文介绍如何利用 Java 8+ Stream API 和自定义分组逻辑,将 List 按 fileSize 累计和不超过阈值的原则,高效划分为多个 List 子列表,最终返回 List。
本文介绍如何利用 java 8+ stream api 和自定义分组逻辑,将 `list>`。
在实际文件批处理、分片上传或资源调度等场景中,常需将一批对象(如 FileInfo)按其某个数值型属性(如 fileSize)的累计和进行动态分组,确保每个分组的总大小不超过指定上限(例如 200 MB)。虽然传统 for 循环易于理解,但使用 Stream 实现可提升代码声明性与函数式表达力——关键在于:标准 Collectors.groupingBy 本身不支持基于全局状态的动态分组,因此需借助 AtomicLong 维护运行时累计值,并结合 collectingAndThen 进行后处理。
核心思路是:为每个 FileInfo 计算一个“逻辑分组键”,该键由当前累计 fileSize 除以阈值后向上取整得到(即 ⌈cumulativeSize / limit⌉),从而保证累计和超限时自动进入下一组。注意:该策略依赖流元素顺序(必须保持原始顺序),因此应避免并行流(parallelStream()),否则 AtomicLong 累加结果不可预测。
以下为完整可运行示例:
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.stream.Collectors;
public class FileInfoProcessor {
private static final double FILE_GROUP_LIMIT = 200.0;
public static List<list>> groupByCumulativeSize(List<fileinfo> files) {
AtomicLong cumulative = new AtomicLong(0L);
return files.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
fileInfo -> String.valueOf(
Math.ceil((double) cumulative.addAndGet(fileInfo.fileSize) / FILE_GROUP_LIMIT)
),
LinkedHashMap::new, // 保持插入顺序,确保分组顺序与输入一致
Collectors.toList()
),
map -> new ArrayList(map.values())
));
}
// 示例用法
public static void main(String[] args) {
List<fileinfo> files = List.of(
new FileInfo("A", 100L, "Active"),
new FileInfo("B", 100L, "Active"),
new FileInfo("C", 100L, "Active"),
new FileInfo("D", 100L, "Active")
);
List<list>> grouped = groupByCumulativeSize(files);
grouped.forEach(System.out::println);
// 输出:[[FileInfo{filePath='A', fileSize=100, status='Active'}, FileInfo{filePath='B', fileSize=100, status='Active'}],
// [FileInfo{filePath='C', fileSize=100, status='Active'}, FileInfo{filePath='D', fileSize=100, status='Active'}]]
}
public static class FileInfo {
private final String filePath;
private final Long fileSize;
private final String status;
public FileInfo(String filePath, Long fileSize, String status) {
this.filePath = filePath;
this.fileSize = fileSize;
this.status = status;
}
// 省略 getter/setter 和 toString(如需调试可补全)
@Override
public String toString() {
return "FileInfo{" + "filePath='" + filePath + "', fileSize=" + fileSize + ", status='" + status + "'}";
}
}
}</list></fileinfo></fileinfo></list>
⚠️ 重要注意事项:
- 顺序敏感:此方案严格依赖输入列表顺序。若需稳定分组(如按路径排序后再分组),请先调用 files.stream().sorted(...).collect(...);
- 线程安全限制:AtomicLong 仅保障累加原子性,但 groupingBy 的 Map 构建过程在串行流中是安全的;切勿在并行流中使用本方案;
- 边界情况处理:单个 fileSize > FILE_GROUP_LIMIT 时,该文件将独占一组(因 ⌈x⌉ ≥ 1 且 x > 1);
- 性能考量:相比传统循环,本方案额外创建 Map 和中间对象,大数据量时建议基准测试;若追求极致性能,仍推荐迭代器手动分组。
总结:通过组合 Collectors.groupingBy、AtomicLong 状态累积与 collectingAndThen 后处理,我们以函数式风格优雅实现了“按累计值动态分组”的需求。它体现了 Stream 在复杂收集场景下的扩展能力,但务必牢记其对顺序与并发的隐含约束。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











