代码优化案例

wen java案例 2

本文目录导读:

代码优化案例

  1. 第一版:新手写法(性能差,可读性差)
  2. 第二版:中级优化(使用 Map 和 高效排序)
  3. 第三版:极致优化(针对“只取Top 3”的场景)
  4. 第四版:注重可维护性(结合函数式编程和防御性编程)
  5. 总结对比
  6. 核心优化思想提炼

我给你一个非常经典且高价值的代码优化案例,从性能可读性两个维度进行演进。

以一个常见的业务场景为例:从订单列表中统计每个用户的累计消费金额,并找出消费最高的前3名用户


第一版:新手写法(性能差,可读性差)

// 假设 orders 是一个很大的数组(10万条数据)
const orders = [
    { userId: 'u1', amount: 100 },
    { userId: 'u2', amount: 200 },
    // ... 十万条
];
function getTop3Users(orders) {
    // 1. 用一个数组存储所有用户的汇总信息
    let userArray = [];
    // 2. 遍历订单
    for (let i = 0; i < orders.length; i++) {
        let order = orders[i];
        let found = false;
        // 3. 内层循环:在数组中查找是否已有该用户(O(n) 查找)
        for (let j = 0; j < userArray.length; j++) {
            if (userArray[j].userId === order.userId) {
                userArray[j].total += order.amount;
                found = true;
                break;
            }
        }
        // 4. 如果没找到,新增用户
        if (!found) {
            userArray.push({ userId: order.userId, total: order.amount });
        }
    }
    // 5. 冒泡排序(O(n^2) 排序)
    for (let i = 0; i < userArray.length; i++) {
        for (let j = 0; j < userArray.length - i - 1; j++) {
            if (userArray[j].total < userArray[j+1].total) {
                let temp = userArray[j];
                userArray[j] = userArray[j+1];
                userArray[j+1] = temp;
            }
        }
    }
    // 6. 取前三个
    let result = [];
    for (let i = 0; i < 3 && i < userArray.length; i++) {
        result.push(userArray[i]);
    }
    return result;
}

问题分析:

  1. 时间复杂度过高:查找用户用了 O(n) 的内层循环,导致总复杂度为 O(n²),大数据量下会卡死。
  2. 排序效率低:用了冒泡排序 O(n²),实际上只需要前3名,用堆或局部排序即可。
  3. 代码冗长:可读性极差,嵌套循环多,容易出bug。

第二版:中级优化(使用 Map 和 高效排序)

function getTop3Users(orders) {
    // 1. 使用 Map 将查找从 O(n) 降为 O(1)
    const userMap = new Map();
    // 2. 一次遍历,累加金额
    for (const order of orders) {
        // 如果存在则累加,不存在则初始化
        userMap.set(order.userId, (userMap.get(order.userId) || 0) + order.amount);
    }
    // 3. 将 Map 转为数组
    const userArray = Array.from(userMap, ([userId, total]) => ({ userId, total }));
    // 4. 使用原生 sort(V8 引擎下是快排,O(n log n))
    userArray.sort((a, b) => b.total - a.total);
    // 5. 取前3个
    return userArray.slice(0, 3);
}

优化效果:

  • 时间复杂度从 O(n²) 降到了 O(n log n)(排序主导)。
  • 代码量减少了一半,逻辑清晰。
  • 利用了 Map 和原生 sort,性能大幅提升。

第三版:极致优化(针对“只取Top 3”的场景)

当用户量非常大(比如几百万用户)时,全量排序(O(n log n))仍然有点浪费,我们只需要最大的3个,不需要全排序,可以用最小堆或者维护一个小数组

function getTop3Users(orders) {
    // 1. 聚合(保持不变,用 Map 线性时间 O(n))
    const userMap = new Map();
    for (const order of orders) {
        userMap.set(order.userId, (userMap.get(order.userId) || 0) + order.amount);
    }
    // 2. 维护一个大小固定为3的“小顶堆”(用数组模拟)
    const top3 = [];
    // 3. 一次遍历所有用户,只维护Top3
    for (const [userId, total] of userMap) {
        if (top3.length < 3) {
            // 堆未满,直接加入并上浮(保持堆序)
            top3.push({ userId, total });
            // 上浮操作(因为很小,直接比较简单排序)
            top3.sort((a, b) => a.total - b.total); // 小顶堆:最小值在开头
        } else if (total > top3[0].total) {
            // 如果当前用户金额大于堆顶(最小值),替换堆顶
            top3[0] = { userId, total };
            // 下沉操作(保持堆序)
            top3.sort((a, b) => a.total - b.total);
        }
    }
    // 4. 因为是小顶堆(升序),反转得到降序
    return top3.reverse();
}

最终性能:

  • 时间复杂度降为 O(n)(聚合是 O(n),后面的 Top 3 维护是 O(1)),非常适合海量数据处理。
  • 空间复杂度 O(m),m为用户数量(必须有地方存数据)。

第四版:注重可维护性(结合函数式编程和防御性编程)

在实际项目中,可读性和健壮性往往比微优化更重要,在保持 O(n) 的基础上,把逻辑拆成小函数:

// 1. 聚合函数:将订单流聚合为用户消费总额
function aggregateUserTotals(orders) {
    return orders.reduce((map, order) => {
        map.set(order.userId, (map.get(order.userId) || 0) + order.amount);
        return map;
    }, new Map());
}
// 2. 获取Top N(支持泛化,不硬编码3)
function getTopN(map, n) {
    // 使用迭代器,避免创建大数组
    const iterator = map.entries();
    const minHeap = [];
    // 实现一个简单的堆(这里省略堆的完整实现,用简化的数组+sort演示)
    // 注意:n很小的时候,直接sort比手动建堆更高效
    for (const [userId, total] of iterator) {
        minHeap.push({ userId, total });
    }
    // 如果要极致性能且n很大,用真正的堆;如果n很小(lt;10),sort反而快
    minHeap.sort((a, b) => b.total - a.total);
    return minHeap.slice(0, n);
}
// 3. 主流程
function getTop3Users(orders) {
    if (!Array.isArray(orders) || orders.length === 0) {
        return [];
    }
    const totals = aggregateUserTotals(orders);
    return getTopN(totals, 3);
}

总结对比

版本 时间复杂度 空间复杂度 可读性 适用场景
第一版 O(n²) O(m) 几乎不适用
第二版 O(n log n) O(m) 中等 小到中量数据
第三版 O(n) O(m) 中等 海量数据(Top K问题)
第四版 O(n) O(m) 优秀 工程实践,兼顾性能与维护

核心优化思想提炼

  1. 数据结构的选择:从数组+循环查找哈希表(Map),将查找从 O(n) 变为 O(1)
  2. 减少不必要的计算:从全排序只维护Top K,避免了对所有用户进行排序。
  3. 利用语言内置优化:使用原生 Mapreducesort,它们在V8引擎中有深度优化。
  4. 可读性与性能的平衡:第三版性能最好,但堆逻辑稍复杂;第四版在性能损失极小的情况下,让代码逻辑分层清晰,更利于团队协作。

希望这个案例对你有启发!如果你有具体的代码片段需要优化,也可以发给我看看。

抱歉,评论功能暂时关闭!