Java 17 Spring Webflux优化ArrayList单次流处理计算方案咨询
单次遍历完成多字段统计的高效实现方案
先修正基础问题
首先指出两处影响功能和性能的问题:
- 实体类字段类型不合理:
numberOfDays、totalHours等数值型字段用String存储,每次计算都要解析字符串,既影响性能还可能抛出NumberFormatException,建议直接改为数值类型(比如Double或BigDecimal,根据业务精度需求选择):
@Column(name = "NUMBER_OF_DAYS") private Double numberOfDays; @Column(name = "TOTAL_HOURS") private Double totalHours; // 其他数值字段同理修改
- 仓库方法参数错误:实体类中
date是LocalDate类型,但仓库方法的date参数是int,完全不匹配,会导致查询失败,修正为:
@Query("FROM EmployeeDO e WHERE e.deptId =:deptId and e.date =:date ORDER BY e.date ASC") List<EmployeeDO> getEmployeeRec(@Param("deptId") int deptId, @Param("date") LocalDate date);
单次遍历实现多统计的方案
我们可以自定义一个统计结果DTO,用来存储所有需要的总和、计数(平均值=总和/计数),然后通过单次遍历完成所有数据收集,有两种实现方式:
方式1:使用Stream.reduce(函数式风格)
先定义统计DTO:
@Data @Builder @AllArgsConstructor @NoArgsConstructor class EmployeeStats { // 存储各字段总和 private double totalDays; private double totalHours; private double totalManpower; private double totalWages; // 记录数据条数,用于计算平均值 private long count; // 提供平均值计算方法 public double getAvgDays() { return count == 0 ? 0 : totalDays / count; } public double getAvgHours() { return count == 0 ? 0 : totalHours / count; } public double getAvgWages() { return count == 0 ? 0 : totalWages / count; } }
然后用reduce单次遍历集合:
var empList = employeeDao.getEmployeeRec(100, LocalDate.parse("2023-08-14")); EmployeeStats stats = empList.stream() .reduce( // 初始化累加器 EmployeeStats.builder() .totalDays(0) .totalHours(0) .totalManpower(0) .totalWages(0) .count(0) .build(), // 遍历每个元素,更新累加器 (acc, emp) -> { acc.setTotalDays(acc.getTotalDays() + emp.getNumberOfDays()); acc.setTotalHours(acc.getTotalHours() + emp.getTotalHours()); acc.setTotalManpower(acc.getTotalManpower() + emp.getTotalManpower()); acc.setTotalWages(acc.getTotalWages() + emp.getTotalWages()); acc.setCount(acc.getCount() + 1); return acc; }, // 并行流场景下合并两个累加器(串行流不会执行此逻辑) (stats1, stats2) -> { stats1.setTotalDays(stats1.getTotalDays() + stats2.getTotalDays()); stats1.setTotalHours(stats1.getTotalHours() + stats2.getTotalHours()); stats1.setTotalManpower(stats1.getTotalManpower() + stats2.getTotalManpower()); stats1.setTotalWages(stats1.getTotalWages() + stats2.getTotalWages()); stats1.setCount(stats1.getCount() + stats2.getCount()); return stats1; } ); // 获取最终统计结果 double totalDays = stats.getTotalDays(); double avgHours = stats.getAvgHours(); double totalManpower = stats.getTotalManpower(); double avgWages = stats.getAvgWages();
方式2:传统for循环(更直观,性能略优)
如果觉得Stream的写法不够直观,直接用for循环遍历,性能和reduce相当,甚至在小数据量下更高效:
var empList = employeeDao.getEmployeeRec(100, LocalDate.parse("2023-08-14")); EmployeeStats stats = new EmployeeStats(0, 0, 0, 0, 0); for (EmployeeDO emp : empList) { stats.setTotalDays(stats.getTotalDays() + emp.getNumberOfDays()); stats.setTotalHours(stats.getTotalHours() + emp.getTotalHours()); stats.setTotalManpower(stats.getTotalManpower() + emp.getTotalManpower()); stats.setTotalWages(stats.getTotalWages() + emp.getTotalWages()); stats.setCount(stats.getCount() + 1); } // 同样通过stats的get方法获取总和、平均值
方案优势
- 仅遍历集合一次,避免了多次遍历带来的性能开销,数据量越大优势越明显
- 所有统计逻辑集中管理,便于维护
- 修正了原代码中的类型错误,避免潜在的运行时异常
内容的提问来源于stack exchange,提问作者Senthilkumar P
相关产品推荐
相关产品推荐

