公司来的新同事,把代码耗时从 26856ms 优化到了 748ms。。。

文摘   2024-11-11 13:29   陕西  
天想和大家聊聊我公司新来的同事。他刚上班没几天,就把一段需要比对数据的代码,从耗时 26856ms 优化到了 748ms

对,没听错!差不多是35倍的提升,我看到这成绩的时候,差点没把我的咖啡喷出来。

到底他用了什么神仙操作?咱们今天来扒一扒。

起因:双重 for 循环的性能灾难

事情是这样的,公司有段代码需要对两个列表中的数据进行比对,也就是找到两个列表中相同 ID 的元素。最开始的代码用的是经典的双重 for 循环,结构大概是这样的:
let commonItems = [];
for (let i = 0; i < listA.length; i++) {
  for (let j = 0; j < listB.length; j++) {
    if (listA[i].id === listB[j].id) {
      commonItems.push(listA[i]);
      break;  // 一定要加这个 break,不然性能更差
    }
  }
}
这个代码逻辑很简单:遍历 listAlistB,每找到一个相同 ID 就加入结果数组。理论上没毛病,但在实际应用中,这段代码简直是个性能黑洞——因为它的时间复杂度是 **O(n²)**。随着数据量一大,这个双重循环分分钟让你的服务器开始喝咖啡(假装很忙,但其实啥也没干)。

第一波优化:break 来救场

这个新同事上来看到代码,第一步就是加了个 break,毕竟找到相同的 ID 后就没必要再继续比对下去了。于是代码就改成了上面那段,加了 break 的样子。优化之后,运行时间确实有所减少,但依然不够“给力”。因为即便加了 break,时间复杂度还是 **O(n²)**,只不过节省了内层的一部分循环。
虽然加 break 是好心,但在大数据量面前,依然是杯水车薪。新同事看了一眼测试结果,默默叹了口气,开始捋袖子,继续搞事了。

高级操作:用 Map 秒杀双重循环

大数据面前,for 嵌套 for 就是渣渣。于是,这位老哥决定采取更强力的优化方式:把 listB 转换成 Map,利用哈希表的 O(1) 查找特性来减少内层的循环次数,整个思路就是让查找变成一步到位。
怎么操作?简单得让人感动——直接上 Map。大致代码如下:
// Step 1:把 listB 转换成 Map,以 ID 为键,元素本身为值
let mapB = new Map();
for (let item of listB) {
  mapB.set(item.id, item);
}

// Step 2:用 mapB 来快速查找 listA 中的共同元素
let commonItems = [];
for (let item of listA) {
  if (mapB.has(item.id)) {
    commonItems.push(item);
  }
}
这种方式直接将时间复杂度从 O(n²) 优化到了 **O(n)**。原理是什么?原理就是通过 Map 的 O(1) 查找特性,替代了原来的双重循环。这样一来,遍历 listA 的时候,只需要查一下 mapB 中有没有相同的 id 就可以了,连内层循环都省了,查询时间直接砍到最低。

为什么 Map 能加速?

Map 的本质是基于哈希表实现的。在 JavaScript 中,Map 提供了接近于 O(1) 的查找效率,因为它通过哈希算法把键映射到一个固定位置,可以迅速找到元素的位置。相比传统的数组,哈希表的查找方式要高效得多,这也是为什么把列表转成 Map 可以实现这么大的性能提升。
所以,经过这一步,代码的运行时间直接从最初的 26856ms 优化到了 748ms,堪称“质的飞跃”。不光服务器不再喝咖啡了,大家的工位上空气都清新了许多。

看看效果:性能对比一目了然

优化前后,代码耗时直接从26856ms降到748ms,这种效率提升,简直不输打了鸡血。可见在大数据量处理中,算法的选择确实能带来天壤之别。

关于内存的权衡

当然,优化总是有代价的,Map 虽然查找效率高,但也有不小的内存开销,尤其当 listB 里的数据量非常大时,把它整个加载到内存中可能会有些“心塞”。所以实际应用中要具体情况具体分析:如果数据量不大,完全可以坚持双重循环,再加个 break。但在需要频繁查找的情况下,Map 是个非常值得的选择。
另外一个小技巧,假设 listAlistB 的数据量差异较大,那么可以选择更小的列表来生成 Map,这样可以减少内存占用,同时依然能享受到 Map 带来的查找加速。

小结一下代码的应用场景

这种 Map 优化方法特别适合以下场景:
  1. 数据比对:当你需要对两个列表中的数据进行对比查找时,用 Map 可以极大地减少循环次数。
  2. 频繁查找操作:如果数据需要频繁查找,不妨先用 Map 做预处理,再进行后续操作。
  3. 大数据量场景Map 在处理大数据量时能够显著提高效率,特别适合服务端一些高并发、需要快速响应的业务需求。

最后的几个优化小tips

  1. 避免多重循环:当代码里出现多重循环时,就应该警惕了,尽量看看能否通过预处理来降低复杂度。
  2. 数据结构选对很重要:数据结构的选择对性能有着巨大的影响,特别是面对大数据量的情况。Map 是查找加速的一个好帮手,但也别忘了常见的对象 key-value 映射,JavaScript 对象也是不错的选择。
  3. 合理分配内存:在性能和内存之间找平衡点,不要为了追求性能盲目使用内存。在一些内存敏感的应用中,可以考虑适当减少预加载数据的大小。

这就是同事从 26856ms 优化到 748ms 的故事,一个小 Map 就救了场,当然这背后少不了对数据结构的理解和对代码优化的探索。

要是遇到大数据,还在用双重循环的小伙伴们,不妨试试 Map 吧,说不定下一位被夸的就是你了 😎

-END-

ok,今天先说到这,老规矩,看完文章记得右下角给何老师点赞。

最后送给大家一个福利,我这里有一份搞副业的教程,这份教程里有100+个搞钱小项目

网盘拉新核心玩法、公众号运营变现、小红书虚拟资料引流等,现在扫码加我微信,即可领取这份副业教程。

添加时备注:副业

程序员老鬼
10年+老程序员,专注于AI知识普及,已打造多门AI课程,本号主要分享国内AI工具、AI绘画提示词、Chat教程、AI换脸、Chat中文指令、Sora教程等,帮助读者解决AI工具使用疑难问题。
 最新文章