ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、视频、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
在实际开发中,**双重 for 循环** 是性能杀手。如果两个集合的大小分别是 M 和 N,双重循环的时间复杂度是 O(M * N) 通过使用 **Map**,我们可以将复杂度降低到 O(M + N)。这种技术通常被称为**空间换时间**。 * * * ### 1\. 业务场景举例 假设你有两个集合: 1. **账单列表 (Bill List)**:包含 `billId` 和 `amount`。 2. **详情列表 (Detail List)**:包含 `billId` 和 `remark`。 **任务**:将 `remark` 合并到对应的账单对象中。 #### **传统做法:双重 for 循环 (低效)** 每一次外部循环都要全量扫描内部循环,如果数据量各有 1 万条,就要对比 1 亿次。 Java ~~~ for (Bill bill : bills) { for (Detail detail : details) { if (bill.getBillId().equals(detail.getBillId())) { // 每次都要找 bill.setRemark(detail.getRemark()); break; } } } ~~~ * * * ### 2\. 优化做法:Map 索引化 (高效) **核心思想**:先遍历一遍详情列表,把它们存入一个 Map,其中 `Key` 是关联字段(billId),`Value` 是对象本身。 #### **优化步骤代码:** Java ~~~ // 第一步:将详情列表转化为 Map (Key 是 ID,Value 是详情对象) // 复杂度:O(N) Map<String, Detail> detailMap = details.stream() .collect(Collectors.toMap(Detail::getBillId, d -> d, (v1, v2) -> v1)); // 第二步:遍历账单列表,直接从 Map 中“抓取”数据 // 复杂度:O(M) for (Bill bill : bills) { Detail detail = detailMap.get(bill.getBillId()); // 这里的查找速度是 O(1) if (detail != null) { bill.setRemark(detail.getRemark()); } } ~~~ * * * ### 3\. 性能对比表格 假设 $M=10,000$,$N=10,000$: | **维度** | **双重 for 循环** | **Map 优化方案** | **结论** | | --- | --- | --- | --- | | **查找机制** | 线性扫描(一个个数) | 哈希查找(直接定位) | Map 极快 | | **计算次数** | $10,000 \\times 10,000 = 1$ 亿次 | $10,000 + 10,000 = 2$ 万次 | **提升 5000 倍** | | **时间复杂度** | $O(M \\times N)$ | $O(M + N)$ | 线性增长 | | **空间消耗** | 极低 | 较高(需要内存存 Map) | 空间换时间 | * * * ### 4\. 什么时候用这个方案? * **数据量大时**:当两个集合的数据量都超过 100 条,建议使用 Map 优化。 * **频繁匹配时**:如果在一个复杂的算法中需要反复查找某个 ID 对应的信息。 * **内存充足时**:Map 会占用一定的堆内存,如果内存极其紧张(如嵌入式开发),需要权衡。 * * * ### 5\. 进阶技巧:分组聚合 (`groupingBy`) 有时候,一个 ID 对应多个详情(一对多),你可以使用 Java 8 的 `groupingBy`: Java ~~~ // 将详情按 billId 分组,Map 的 Value 是一个 List Map<String, List<Detail>> detailGroup = details.stream() .collect(Collectors.groupingBy(Detail::getBillId)); // 使用时 List<Detail> subDetails = detailGroup.get(bill.getBillId()); ~~~ ### 下一步建议 这种 Map 优化的思想在 **SQL 联表查询 (Join)** 的底层实现中也经常用到。