【Java】性能优化——使用Map实现多列表数据合并

前言

在实际开发中,经常会有需求是在多张表中查找具有相同字段的数据,然后将这多张表中的数据按照需求进行合并,许多开发者会使用for 循环嵌套。这种写法效率较低且不够优雅,本文提供一种使用Map合并数据的优化方法。


1.示例

假设我们有三个返回类:

@Data
public class LaborCostDetailRes {
    /**
     * 年月
     */
    private String date;
    /**
     * 人工成本
     */
    private BigDecimal laborCost;
}
@Data
public class OtherCostDetailRes {
    /**
     * 年月
     */
    private String date;
    /**
     * 其他成本
     */
    private BigDecimal otherCost;

}
@Data
public class CashFlowRes {
    /**
     * 年月
     */
    private String date;
    /**
     * 销售收入
     */
    private BigDecimal saleAmount;
    /**
     * 人工成本
     */
    private BigDecimal laborCost;
    /**
     * 其他成本
     */
    private BigDecimal otherCost;

}

而我们需要将LaborCostDetailReslaborCostOtherCostDetailResotherCost设置到CashFlowRes的对应字段中,通常我们可能这样写:

// 查询出所有的CashFlowRes集合
List<CashFlowRes> cashFlowRes = cashFlowMapper.list();
// 查询出所有LaborCostDetailRes集合
List<LaborCostDetailRes> laborCostDetailRes = staffSalaryMapper.listLaborCost();
// 查询出所有otherCostDetailRes集合
List<OtherCostDetailRes> otherCostDetailRes = costManagementMapper.listOtherCost();

for (CashFlowRes cashFlow : cashFlowRes) {
	String cashFlowDate = cashFlow.getDate();

	// 遍历laborCostDetailRes
	for (LaborCostDetailRes laborCost : laborCostDetailRes) {
		String laborCostDate = laborCost.getDate();
		if (cashFlowDate.equals(laborCostDate)) {
			cashFlow.setLaborCost(laborCost.getLaborCost());
			break; 
		}
	}

	// 遍历otherCostDetailRes
	for (OtherCostDetailRes otherCost : otherCostDetailRes) {
		String otherCostDate = otherCost.getDate();
		if (cashFlowDate.equals(otherCostDate)) {
			cashFlow.setOtherCost(otherCost.getOtherCost());
			break; 
		}
	}
}

时间复杂度分析

1.外层循环:

  • 外层循环遍历 cashFlowRes 列表,假设 cashFlowRes 的长度为 n。

2.内层循环遍历 laborCostDetailRes:

  • 对于 cashFlowRes 中的每个元素,内层循环遍历 laborCostDetailRes 列表,假设 laborCostDetailRes 的长度为 m。
  • 在最坏情况下,内层循环需要遍历整个 laborCostDetailRes 列表才能找到匹配的日期。
  • 因此,内层循环的时间复杂度为 O(m)。

3.内层循环遍历 otherCostDetailRes:

  • 同样地,对于 cashFlowRes 中的每个元素,内层循环遍历 otherCostDetailRes 列表,假设 otherCostDetailRes 的长度为 p。
  • 在最坏情况下,内层循环需要遍历整个 otherCostDetailRes 列表才能找到匹配的日期。
  • 因此,内层循环的时间复杂度为 O( p)。

总时间复杂度

将上述时间复杂度结合起来,整个方法的时间复杂度为:

  • 外层循环的时间复杂度为 O(n)。
  • 每次外层循环中,有两个内层循环,分别的时间复杂度为 O(m) 和 O( p)。

因此,总的时间复杂度为:O(n * (m + p))


这种方法在数据量较大时可能会导致性能问题,因为时间复杂度较高。如果数据量较大,建议使用 Map 来优化性能。

// 查询出所有的CashFlowRes集合
List<CashFlowRes> cashFlowRes = cashFlowMapper.list();
// 查询出所有LaborCostDetailRes集合
List<LaborCostDetailRes> laborCostDetailRes = staffSalaryMapper.listLaborCost();
// 查询出所有otherCostDetailRes集合
List<OtherCostDetailRes> otherCostDetailRes = costManagementMapper.listOtherCost();

mergeCashFlow(laborCostDetailRes,otherCostDetailRes,cashFlowPOList);

public void mergeCashFlow(List<LaborCostDetailRes> laborCostDetailRes,
						  List<OtherCostDetailRes> otherCostDetailRes,
						  List<CashFlowPO> cashFlowPOList) {

	Map<String, BigDecimal> laborMap = createMap(laborCostDetailRes, LaborCostDetailRes::getDate, LaborCostDetailRes::getLaborCost);
	Map<String, BigDecimal> otherMap = createMap(otherCostDetailRes, OtherCostDetailRes::getDate, OtherCostDetailRes::getOtherCost);

	if (cashFlowResList != null) {
		for (CashFlowRes cashFlowRes : cashFlowResList) {
			if (cashFlowRes!= null && cashFlowRes.getDate() != null) {
				String yearMonth = cashFlowRes.getDate();
				cashFlowRes.setLaborCost(laborMap.getOrDefault(yearMonth, BigDecimal.ZERO));
				cashFlowRes.setOtherCost(otherMap.getOrDefault(yearMonth, BigDecimal.ZERO));
			}
		}
	}
}

/**
* 转换为一个日期到金额的 Map映射
* @param list
* @param dateExtractor
* @param amountExtractor
* @return
* @param <T>
*/
private <T> Map<String, BigDecimal> createMap(List<T> list, Function<T, String> dateExtractor, Function<T, BigDecimal> amountExtractor) {
	Map<String, BigDecimal> map = new HashMap<>();
	if (list != null) {
		for (T item : list) {
			if (item != null) {
				String date = dateExtractor.apply(item);
				BigDecimal amount = amountExtractor.apply(item);
				if (date != null) {
					map.put(date, amount);
				}
			}
		}
	}
	return map;
}

时间复杂度分析

1.createMap 方法:

  • createMap 方法的时间复杂度为 O(k),其中 k 是列表的长度。
  • 对于 laborCostDetailRes,时间复杂度为 O(m),其中 m 是 laborCostDetailRes 的长度。
  • 对于 otherCostDetailRes,时间复杂度为 O( p),其中 p 是 otherCostDetailRes 的长度。

2.mergeCashFlow 方法:

  • 遍历 cashFlowResList 的时间复杂度为 O(n),其中 n 是 cashFlowResList 的长度。
  • 对于每个 CashFlowRes 对象,从 laborMap 和 otherMap 中获取值的时间复杂度为 O(1),因为 HashMap 的查找操作平均时间复杂度为 O(1)。

总时间复杂度

将上述时间复杂度结合起来,整个方法的时间复杂度为:

  • 查询数据的时间复杂度可以忽略,假设数据已经加载到内存中。
  • createMap 方法的时间复杂度为 O(m + p)。
  • mergeCashFlow 方法的时间复杂度为 O(n)。

因此,总的时间复杂度为:O(m + p + n)


显然,使用 Map 进行数据合并可以显著提高性能,特别是在处理大量数据时,因为查找操作的时间复杂度为 O(1)。

2.总结

特性嵌套 for 循环使用 Map
时间复杂度O(n * (m + p))O(m + p + n)
性能较差,数据量大时性能下降显著较好,数据量大时性能优势明显
代码简洁性简单直观,但代码冗长代码简洁,易于维护和扩展
内存开销高,需要额外的 Map 结构
扩展性难以扩展,添加数据源时代码冗长容易扩展,只需添加更多的 Map

对于大多数实际应用场景,特别是数据量较大的情况,推荐使用 使用 Map 的方法。虽然它需要额外的内存开销,但显著提高了性能和代码的可维护性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值