公司新来了一位小伙伴,把某段代码的执行时间从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 拉你进入“程序员交流群”。