代码层面的优化:让每一行代码都跑得更快

代码优化,说白了就是让计算机少干活。我见过太多团队,一上来就搞架构升级、加机器,结果回头一看,代码里全是坑。今天咱们聊聊代码层面那些立竿见影的优化手段。

算法复杂度分析:选对算法,事半功倍

我个人习惯,写代码前先想清楚复杂度。不是所有场景都需要O(1),但至少别写出O(n²)去处理百万级数据。

举个例子,我接手过一个报表系统,每次生成都要30秒。一查,核心逻辑是个双层循环:

// 优化前:O(n*m) 的噩梦
for (let i = 0; i < users.length; i++) {
  for (let j = 0; j < orders.length; j++) {
    if (orders[j].userId === users[i].id) {
      // 处理逻辑
    }
  }
}

改成哈希表后,直接降到O(n+m):

// 优化后:O(n+m)
const userMap = new Map();
users.forEach(u => userMap.set(u.id, u));

orders.forEach(order => {
  const user = userMap.get(order.userId);
  if (user) {
    // 处理逻辑
  }
});

运行时间从30秒降到了0.8秒。你想想看,只是换了个数据结构,效果天差地别。

核心原则:能用O(log n)别用O(n),能用O(n)别用O(n²)。数据量每翻10倍,复杂度差一级,性能就差一个数量级。

减少循环嵌套:扁平化才是王道

我在项目中遇到过一段代码,三层嵌套循环处理订单数据。当时我就觉得不对劲,一分析,复杂度O(n³)。

为什么会这样?很多时候是因为我们习惯性用循环解决问题。其实很多嵌套循环可以用以下方式替代:

  • 提前过滤:先filter掉不需要的数据,再处理
  • 空间换时间:用Map、Set等结构减少查找次数
  • 分批处理:大数据量时,拆成小批次并行处理

我曾经优化过一个库存扣减逻辑,原来是这样:

// 三层嵌套,不忍直视
for (let warehouse of warehouses) {
  for (let shelf of warehouse.shelves) {
    for (let item of shelf.items) {
      if (item.sku === targetSku) {
        // 扣减库存
      }
    }
  }
}

改成用SKU索引后:

// 一层循环 + 索引
const skuIndex = buildSkuIndex(warehouses);
const target = skuIndex.get(targetSku);
if (target) {
  target.quantity -= amount;
}

嗯,这里要注意:不是所有嵌套都能消除,但至少可以降到两层以内。超过两层的循环,基本就是代码坏味道了。

使用高效的数据结构:选对容器,事半功倍

很多开发者对数据结构的理解停留在「能用就行」。我见过有人用数组做频繁的插入删除操作,也见过用对象当队列用的。这些习惯,在数据量小的时候没问题,一旦上了规模,就是性能杀手。

操作场景 推荐结构 不推荐结构 原因
频繁查找 Map / Set Array Map查找O(1),Array查找O(n)
频繁插入/删除 LinkedList Array Array插入删除需要移动元素
先进先出 Queue Array.shift() shift()会重新索引所有元素
去重 Set Array + indexOf Set去重O(n),indexOf去重O(n²)

我记得有一次优化一个实时推荐系统,原来用数组存用户行为数据,每次查找都要遍历。换成Map之后,响应时间从200ms降到了5ms。说白了,数据结构选对了,性能优化就完成了一半。

小技巧:JavaScript中,for...of遍历Map比Object.keys()遍历对象快30%左右。遇到频繁遍历的场景,优先考虑Map。

避免重复计算:缓存是好朋友

重复计算是代码里最常见的性能浪费。我见过一个计算订单总价的函数,每次调用都重新计算所有商品的折扣。其实折扣信息一周才变一次,完全没必要每次都算。

常见的重复计算场景:

  • 循环内的不变计算:把循环外的计算提到外面
  • 重复的函数调用:结果缓存起来,下次直接用
  • 重复的DOM查询:用变量保存查询结果
  • 重复的API调用:加一层缓存层
// 优化前:每次循环都计算一次
for (let i = 0; i < items.length; i++) {
  const discount = getDiscount(items[i].category); // 每次都要查数据库
  items[i].price = items[i].price * discount;
}

// 优化后:缓存折扣信息
const discountCache = new Map();
for (let i = 0; i < items.length; i++) {
  const category = items[i].category;
  if (!discountCache.has(category)) {
    discountCache.set(category, getDiscount(category));
  }
  items[i].price = items[i].price * discountCache.get(category);
}

我曾经犯过一个错误:在递归函数里重复计算斐波那契数列。n=40的时候,程序直接卡死。加上记忆化缓存后,瞬间出结果。避坑指南:递归函数一定要考虑重复计算的问题,加个缓存往往能指数级提升性能。

注意:缓存虽好,但要注意缓存失效策略。我见过有人把用户信息缓存了24小时,结果用户改了密码还在用旧数据。缓存一定要设置合理的过期时间。

知识体系总览

下面这张图总结了代码层面优化的核心思路,我把它画成了流程图,方便你理解各个优化点之间的关系:

代码层面优化核心路径 代码性能优化 算法复杂度分析 减少循环嵌套 高效数据结构 O(1) > O(log n) > O(n) 避免O(n²)以上 提前过滤数据 空间换时间 Map/Set优先 避免数组shift 避免重复计算 缓存计算结果 循环外提不变式

这张图把四个优化方向串起来了。你从中心出发,沿着任意一条分支走到底,都能找到具体的优化手段。我个人建议,每次做代码审查时,就拿这张图对照一下,看看代码踩了哪些坑。

最后说一句:代码优化不是一蹴而就的事。我习惯在每次提交代码前,花5分钟检查一下有没有明显的性能问题。养成习惯后,你会发现写出高性能代码其实没那么难。

可可精品资料库