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

科技   2024-11-10 11:31   山西  

公司新来了一位小伙伴,把某段代码的执行时间从26856ms给硬生生降到了748ms。

我的第一反应是:“行啊,高手啊!”然后我就赶紧去扒了一下这个操作的技术细节。咱一起来看看他是怎么做到的吧,顺便分享一些干货,让大家少走点弯路。

1. 问题背景:那令人头疼的双重 for 循环

很多同学在做数据比对的时候,习惯用双重 for 循环,比如要查找两个表中相同 id 的数据。这种方式虽然能找到匹配项,但效率简直低得离谱,尤其当数据量一上来,执行时间就跟火箭一样飞涨。咱们用个例子来看看:

假设有两个列表:

  • User:包含用户的基本信息。
  • UserMemo:包含用户的备注信息。

要求就是查找两个列表中相同 id 的记录。这种情况下,很多人就会写出类似这样的代码:

for (User user : users) {
    for (UserMemo memo : userMemos) {
        if (user.getId().equals(memo.getUserId())) {
            // 匹配到 id,执行相应逻辑
        }
    }
}

嗯,看起来挺顺畅吧?但有一个问题:**时间复杂度是 O(n²)**。假设 users 有 10000 条数据,userMemos 也有 10000 条数据,那就会有一亿次循环。

难怪执行时间能拖到26856ms!这种写法,就好比你要找一个人,却硬是挨家挨户地敲门,效率感人……

2. 基础优化:加个 break,总比啥都不做强

新同事的第一个操作特别简单,直接在内层循环中加了个 break。逻辑很简单,当找到匹配项时,立即跳出循环,减少无谓的比对。这是很多优化的入门技巧。

代码优化后是这样:

for (User user : users) {
    for (UserMemo memo : userMemos) {
        if (user.getId().equals(memo.getUserId())) {
            // 找到匹配项,执行相应逻辑
            break// 找到后立即跳出,减少多余的循环
        }
    }
}

经过测试,加上 break 后,虽然执行时间没有降到“飞速”那种程度,但耗时确实减少了不少,大概降了一半左右。这一招虽然简单,但在数据量不是特别大的情况下能派上用场,算是入门级的优化。不过,想要质的飞跃,这还远远不够。

3. 进一步优化:使用 Map,直接定位

好戏来了,新同事接下来的操作是关键。他直接把 UserMemo 的数据转换成了一个 Map,然后在外层循环中通过 id 直接进行查找,省去了内层循环。相当于:与其一个个去敲门,不如直接打电话找到人家具体住址,一步到位!

Map 的妙用

Map 的特点是能通过 key 实现 O(1) 的查找时间复杂度,避免了逐一比对。用这个思路优化后的代码是这样的:

// 首先将 UserMemo 转换为 Map,key 为用户 id,value 为备注信息
Map<String, UserMemo> memoMap = userMemos.stream()
                                          .collect(Collectors.toMap(UserMemo::getUserId, memo -> memo));

for (User user : users) {
    UserMemo memo = memoMap.get(user.getId()); // 直接通过 id 查找
    if (memo != null) {
        // 找到匹配项,执行相应逻辑
    }
}

这个优化一上来,时间复杂度直接从 O(n²) 降到了 O(n),结果不夸张——原本的 26856ms,瞬间变成了 748ms,整整快了几十倍!这效果就像从“骑驴”变成了“开火箭”,效率质变。

4. 原理解析:时间复杂度 O(n²) 到 O(n) 的跨越

来分析一下为什么优化后的效果能这么显著。原本的双重 for 循环,时间复杂度是 O(n²),因为每个 user 都需要遍历一遍 userMemos。而在使用 Map 后,查找操作的复杂度降到了 O(1),外层循环的复杂度是 O(n),总时间复杂度也就降成了 O(n)。

这段代码的核心在于提前把 UserMemo 转换为一个哈希映射表。Map 的查找操作基于哈希,通常情况下效率非常高,不需要逐个遍历就能定位到目标对象。这一优化技巧,堪称“查找类操作”的必备神器。

如果把代码比作搬砖,那么优化前是你一块砖一块砖搬;而用上 Map 后,感觉就像有了叉车,效率飙升。

5. 总结:何时该用 Map?

是不是所有情况下都可以用 Map 优化呢?也不一定。Map 虽然查找效率高,但也有内存开销,而且适用于键值对操作的场景。对于简单的处理逻辑、数据量不大时,双重循环加上 break 已经足够。但如果你知道查找操作很多、数据量较大,那么一上来就该考虑 Map

当然,有些数据的处理可能会更复杂,比如需要多条件匹配、多层级嵌套查找,甚至一些机器学习模型来匹配的场景,这时优化方案就不止 Map 了。不过,对于日常数据对比查找,Map 优化基本够用。

这段代码优化,简单来说就是从“线性查找”到“哈希定位”的转变,差别如同“爬楼梯”到“坐电梯”。

希望这次的分享能给大家一点灵感,让你在实际工作中碰到类似问题时,多思考几步,不要一股脑就写双重 for 循环。掌握 Map 等数据结构,简直是写高效代码的标配。

对编程、职场感兴趣的同学,可以链接我,微信:coder301 拉你进入“程序员交流群”。
🔥东哥私藏精品 热门推荐🔥
东哥作为一名超级老码农,整理了全网最全《Java高级架构师资料合集》
资料包含了《IDEA视频教程》《最全Java面试题库》、最全项目实战源码及视频》及《毕业设计系统源码》总量高达 650GB 。全部免费领取!全面满足各个阶段程序员的学习需求。

Java面试那些事儿
回复 java ,领取Java面试题。分享AI编程,Java教程,Java面试辅导,Java编程视频,Java下载,Java技术栈,AI工具,Java开源项目,Java简历模板,Java招聘,Java实战,Java面试经验,IDEA教程。
 最新文章