在Java开发中,我们经常需要对集合进行分组操作。本文将深入讲解两种典型的分组场景:基于元素属性的分组和固定大小的分块,帮助您掌握Collectors.groupingBy()
和IntStream
分块的正确使用方式。
一、基于元素属性的分组:groupingBy()
1. 基本用法
Collectors.groupingBy()
是Stream API中用于根据元素属性进行分类的强大工具:
List<Student> students = // 初始化学生列表
// 按照学生所在班级分组
Map<String, List<Student>> studentsByClass = students.stream()
.collect(Collectors.groupingBy(Student::getClassName));
2. 高级用法
2.1 多级分组
// 先按班级分组,再按性别分组
Map<String, Map<Gender, List<Student>>> multiLevelGroup = students.stream()
.collect(Collectors.groupingBy(
Student::getClassName,
Collectors.groupingBy(Student::getGender)
));
2.2 分组后统计
// 统计每个班级的学生人数
Map<String, Long> classCount = students.stream()
.collect(Collectors.groupingBy(
Student::getClassName,
Collectors.counting()
));
3. 使用场景
- 数据分类汇总(如按地区统计销售额)
- 多维度数据分析
- 数据预处理和转换
二、固定大小分块:IntStream方案
1. 基本实现
当需要将列表按固定大小分块时,可以使用IntStream
方案:
List<Map<String, Object>> list = new ArrayList<>();
// 假设list已经被填充了数据
int groupSize = 50;
List<List<Map<String, Object>>> groupedList = IntStream.range(0, (list.size() + groupSize - 1) / groupSize)
.mapToObj(i -> list.subList(i * groupSize, Math.min(list.size(), (i + 1) * groupSize)))
.collect(Collectors.toList());
2. 实现原理
IntStream.range()
生成分组索引序列- 计算总组数:
(list.size() + groupSize - 1) / groupSize
- 使用
subList
获取每个子列表 - 通过
Math.min
处理最后一组可能不足的情况
3. 使用场景
- 批量数据处理(如数据库批量插入)
- 分页处理
- 大数据量分批处理
三、两种分组方式的对比
特性 | groupingBy | 固定大小分块 |
---|---|---|
分组依据 | 元素属性 | 元素位置索引 |
组大小 | 不固定 | 固定大小 |
结果类型 | Map<K, List> | List<List> |
典型应用场景 | 数据分类统计 | 批量处理/分页 |
四、实际应用案例
案例1:电商订单处理
// 按订单状态分组
Map<OrderStatus, List<Order>> ordersByStatus = orders.stream()
.collect(Collectors.groupingBy(Order::getStatus));
// 将订单按每100个一批处理
List<List<Order>> batches = IntStream.range(0, (orders.size() + 99) / 100)
.mapToObj(i -> orders.subList(i * 100, Math.min(orders.size(), (i + 1) * 100)))
.collect(Collectors.toList());
案例2:日志分析
// 按日志级别分组统计
Map<LogLevel, Long> logCountByLevel = logs.stream()
.collect(Collectors.groupingBy(
Log::getLevel,
Collectors.counting()
));
// 将日志按每500条一批写入文件
List<List<Log>> logBatches = IntStream.range(0, (logs.size() + 499) / 500)
.mapToObj(i -> logs.subList(i * 500, Math.min(logs.size(), (i + 1) * 500)))
.collect(Collectors.toList());
五、性能注意事项
-
groupingBy
在数据量大时可能产生性能问题,考虑使用并行流:Map<K, List<T>> result = list.parallelStream() .collect(Collectors.groupingByConcurrent(...));
-
固定大小分块时,
subList
创建的是视图而非新集合,修改会影响原列表 -
对于超大集合,考虑使用迭代器方式逐步处理而非一次性加载
六、总结
- 使用
groupingBy
进行基于属性的逻辑分组 - 使用
IntStream
+subList
进行固定大小的物理分块 - 根据业务场景选择合适的分组策略
- 注意不同分组方式对性能和内存的影响
掌握这两种分组技术,您将能够更高效地处理Java集合数据,满足各种业务场景的需求。