Java 双层 for 循环太慢怎么解决?优化方案来了

AI 概述
这段代码功能是对绩效数据按销售人员进行汇总计算,但存在性能和可维护性问题。核心问题有:时间复杂度高(O(n²))、同一列表重复遍历、if判断和BigDecimal累加逻辑重复、productType的equals写法不安全。优化思路是“一次遍历→一次聚合→Map按人分组”,先按salesOwnerId分组,再遍历一次处理数据,用switch/enum处理产品类型。此外,还可通过枚举加静态方法避免magic value、抽取累加方法减少重复等进一步优化,以应对大数据量情况。
目录
文章目录隐藏
  1. 一、当前代码的核心问题
  2. 二、优化核心思路(重点)
  3. 三、优化写法
  4. 四、进一步进阶优化

Java 双层 for 循环太慢怎么解决?优化方案来了

很多性能问题,并不是因为技术不够,而是因为写的时候没考虑到某些点上,比如数据量变大、时间复杂度等,导致出现很多问题。

来看看下面一段头疼的代码:

public void calculateCmsResult() {
    // 获取绩效列表
    List<CmsPerformanceCalculateVo> performanceVos = cmsPerformanceService.queryCalculateList();
    if(performanceVos == null || performanceVos.isEmpty()){
        return;
    }
    
    // 销售人员 id
    Set<Long> salesOwnerIds = performanceVos.stream()
        .map(CmsPerformanceCalculateVo::getCurrentSalesOwnerId)
        .collect(Collectors.toSet());

    List<CmsResult> resultList = new ArrayList<>();
    for (Long salesOwnerId : salesOwnerIds){
        CmsResult cmsResult = new CmsResult();
        cmsResult.setOperatorUserId(salesOwnerId);
        // 获取上个月第一天
        cmsResult.setPerformanceMonth(DateUtils.getLastMonthFirstDay());

        // 总销售额
        BigDecimal totalSales = BigDecimal.ZERO; 
        // 新品销售额
        BigDecimal newProductSales = BigDecimal.ZERO; 
        // 老品毛利额
        BigDecimal totalProfit = BigDecimal.ZERO; 
        
        // 总毛利额
        BigDecimal totalProfit = BigDecimal.ZERO; 
        // 新品毛利额
        BigDecimal newProductSales = BigDecimal.ZERO; 
        // 老品毛利额
        BigDecimal oldProductProfit = BigDecimal.ZERO;

        for (CmsPerformanceCalculateVo performanceVo : performanceVos) {
            if(performanceVo.getSalesOwnerId().equals(salesOwnerId)){
                totalSales = totalSales.add(performanceVo.getSalesAmount());
                totalProfit = totalProfit.add(performanceVo.getGrossProfit());

                // 新品
                if(performanceVo.getProductType().equals(CmsProductType.NEW_PROD.getDbValue())){
                    newProductSales = newProductSales.add(performanceVo.getSalesAmount());
                    newProductProfit = newProductProfit.add(performanceVo.getGrossProfit());
                }
                
                // 老品
                if(performanceVo.getProductType().equals(CmsProductType.OLD_PROD.getDbValue())){
                    oldProductSales = oldProductSales.add(performanceVo.getSalesAmount());
                    oldProductProfit = oldProductProfit.add(performanceVo.getGrossProfit());
                }
            }
        }

        cmsResult.setTotalSales(totalSales);
        cmsResult.setNewProductSales(newProductSales);
        cmsResult.setOldProductSales(oldProductSales);
        cmsResult.setTotalProfit(totalProfit);
        cmsResult.setNewProductProfit(newProductProfit);
        cmsResult.setOldProductProfit(oldProductProfit);
        resultList.add(cmsResult);
    }
}

功能是对的,但性能和可维护性都有很明显的优化空间,我们来看看怎么优化。

一、当前代码的核心问题

1.时间复杂度过高(O(n²))

for (Long salesOwnerId : salesOwnerIds) {
    for (CmsPerformanceCalculateVo performanceVo : performanceVos) {
        if (performanceVo.getSalesOwnerId().equals(salesOwnerId)) {
            ...
        }
    }
}
  • 假设:
    • 人员数 = M;
    • 绩效数据 = N。
  • 实际复杂度:M × N;
  • 数据量一大(几万行),直接拖垮定时任务。

典型可以用 Map 分组解决。

2.同一个列表被重复遍历

performanceVos 被完整扫了salesOwnerIds.size()

这是最主要的性能浪费点。

3.if 判断 + BigDecimal 累加逻辑重复

newProductSales = newProductSales.add(...)
newProductProfit = newProductProfit.add(...)
  • 代码冗长;
  • 扩展新产品类型时,需要复制粘贴一堆。

4.productType 的 equals 写法不安全

performanceVo.getProductType().equals(...)

如果 getProductType() 为 null,直接 NPE。

二、优化核心思路(重点)

核心优化原则

一次遍历 → 一次聚合 → Map 按人分组。

优化步骤拆解:

  1. 先按 salesOwnerId 分组;
  2. 每个 salesOwnerId 只遍历自己的数据;
  3. 用 switch / enum 处理产品类型;
  4. 减少 BigDecimal 和 if 的重复代码。

三、优化写法

1.先分组(关键一步)

Map<Long, List<CmsPerformanceCalculateVo>> groupMap =
        performanceVos.stream()
            .collect(Collectors.groupingBy(CmsPerformanceCalculateVo::getSalesOwnerId));

2.主逻辑(只遍历一次)

public void calculateCmsResult() {

    List<CmsPerformanceCalculateVo> performanceVos =
            cmsPerformanceService.queryCalculateList();
    if (CollectionUtils.isEmpty(performanceVos)) {
        return;
    }
    
    // 分组
    Map<Long, List<CmsPerformanceCalculateVo>> groupMap =
            performanceVos.stream()
                .collect(Collectors.groupingBy(CmsPerformanceCalculateVo::getSalesOwnerId));

    List<CmsResult> resultList = new ArrayList<>();
    
    for (Map.Entry<Long, List<CmsPerformanceCalculateVo>> entry : groupMap.entrySet()) {

        Long salesOwnerId = entry.getKey();
        List<CmsPerformanceCalculateVo> list = entry.getValue();

        CmsResult cmsResult = new CmsResult();
        cmsResult.setOperatorUserId(salesOwnerId);
        cmsResult.setPerformanceMonth(DateUtils.getLastMonthFirstDay());

        BigDecimal totalSales = BigDecimal.ZERO;
        BigDecimal totalProfit = BigDecimal.ZERO;

        BigDecimal newSales = BigDecimal.ZERO;
        BigDecimal newProfit = BigDecimal.ZERO;

        BigDecimal oldSales = BigDecimal.ZERO;
        BigDecimal oldProfit = BigDecimal.ZERO;

        for (CmsPerformanceCalculateVo vo : list) {

            totalSales = totalSales.add(vo.getSalesAmount());
            totalProfit = totalProfit.add(vo.getGrossProfit());

            Integer productType = vo.getProductType();
            if (productType == null) {
                continue;
            }

            switch (CmsProductType.of(productType)) {
                case NEW_PROD:
                    newSales = newSales.add(vo.getSalesAmount());
                    newProfit = newProfit.add(vo.getGrossProfit());
                    break;
                case OLD_PROD:
                    oldSales = oldSales.add(vo.getSalesAmount());
                    oldProfit = oldProfit.add(vo.getGrossProfit());
                    break;
                default:
                    break;
            }
        }

        cmsResult.setTotalSales(totalSales);
        cmsResult.setTotalProfit(totalProfit);
        cmsResult.setNewProductSales(newSales);
        cmsResult.setNewProductProfit(newProfit);
        cmsResult.setOldProductSales(oldSales);
        cmsResult.setOldProductProfit(oldProfit);

        resultList.add(cmsResult);
    }
}

四、进一步进阶优化

1.枚举加静态方法,避免 magic value

public enum CmsProductType {
    NEW_PROD(1),
    OLD_PROD(2),
    CONTINUE_PROD(3);

    private final Integer dbValue;

    public static CmsProductType of(Integer value) {
        for (CmsProductType type : values()) {
            if (type.dbValue.equals(value)) {
                return type;
            }
        }
        return null;
    }
}

2.抽取累加方法,减少重复

private BigDecimal add(BigDecimal a, BigDecimal b) {
    return a.add(b == null ? BigDecimal.ZERO : b);
}

当数据量还是几百条时,双层循环其实是毫无压力的;

可一旦数据量涨到几万、几十万的时候,问题会直接暴露。

以上关于Java 双层 for 循环太慢怎么解决?优化方案来了的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。

「点点赞赏,手留余香」

1

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » Java 双层 for 循环太慢怎么解决?优化方案来了

发表回复