Boost Java Loop Performance: Replace Nested Loops with Map Lookups
This article demonstrates how to dramatically speed up Java data‑matching operations that use nested for‑loops by breaking early when a match is found and by converting the inner list into a HashMap for O(1) lookups, showing code examples, performance measurements, and practical tips for backend developers.
When you need to match data from two lists—such as a
Userlist and a
UserMemolist—many developers write nested
forloops, which is inefficient for large collections.
<code>@Data
public class User {
private Long userId;
private String name;
}</code> <code>@Data
public class UserMemo {
private Long userId;
private String content;
}</code>Example test data: 50,000
Userobjects and 30,000
UserMemoobjects are generated with random values.
<code>public static List<User> getUserTestList() {
List<User> users = new ArrayList<>();
for (int i = 1; i <= 50000; i++) {
User user = new User();
user.setName(UUID.randomUUID().toString());
user.setUserId((long) i);
users.add(user);
}
return users;
}
public static List<UserMemo> getUserMemoTestList() {
List<UserMemo> userMemos = new ArrayList<>();
for (int i = 30000; i >= 1; i--) {
UserMemo userMemo = new UserMemo();
userMemo.setContent(UUID.randomUUID().toString());
userMemo.setUserId((long) i);
userMemos.add(userMemo);
}
return userMemos;
}</code>The naïve nested‑loop implementation iterates over every
Userand, for each, scans the entire
UserMemolist to find a matching
userId. This results in about 5 × 10⁴ × 3 × 10⁴ ≈ 1.5 billion comparisons, taking roughly 26,857 ms on the test data.
Because each
Useronly needs one matching
UserMemo, we can break out of the inner loop once a match is found. Adding a
breakreduces the runtime to about 10,000 ms, roughly halving the execution time.
A more powerful optimization is to replace the inner loop with a
HashMapthat stores
userId → contentpairs. Building the map once (O(n)) allows O(1) lookups for each
User, turning the overall complexity from O(n²) to O(n).
<code>public static void main(String[] args) {
List<User> userTestList = getUserTestList();
List<UserMemo> userMemoTestList = getUserMemoTestList();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// Build map for fast lookup
Map<Long, String> contentMap = userMemoTestList.stream()
.collect(Collectors.toMap(UserMemo::getUserId, UserMemo::getContent));
for (User user : userTestList) {
Long userId = user.getUserId();
String content = contentMap.get(userId);
if (StringUtils.hasLength(content)) {
System.out.println("模拟数据content 业务处理......" + content);
}
}
stopWatch.stop();
System.out.println("最终耗时" + stopWatch.getTotalTimeMillis());
}</code>Running this version shows a dramatic reduction in execution time (the exact figure is displayed in the chart below), confirming that the hashmap lookup is effectively O(1) for most cases.
The reason for this improvement is the change in time complexity: the original double loop performs a linear scan for each element (O(n²)), while the hashmap provides constant‑time access (O(1)) after a single preprocessing pass. In Java 8’s hash implementation, collisions are rare, so the average lookup cost remains near O(1). Only in the pathological case where all keys hash to the same bucket would performance degrade to O(n), which is unlikely in typical applications.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.