Optimizing Nested Loops in Java: Using Break and Map for Faster Data Matching
This article demonstrates how to improve the performance of nested for‑loops that match user IDs between two large lists by adding a break statement and by converting the inner list into a hash map, reducing execution time from tens of seconds to a few seconds.
The author examines a common scenario where a for loop iterates over a list of User objects and, inside it, another for loop scans a list of UserMemo objects to find the matching userId and process its content . With 50,000 users and 30,000 memos, the naïve double‑loop approach takes about 26,857 ms.
First, the author shows the original data classes:
@Data
public class User {
private Long userId;
private String name;
} @Data
public class UserMemo {
private Long userId;
private String content;
}Test data generators create the two lists:
public static List
getUserTestList() {
List
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
getUserMemoTestList() {
List
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;
}Running the double loop without any early exit yields the long execution time shown above. Adding a break after the match reduces the time to roughly 10,000 ms, because the inner loop stops once the matching memo is found.
for (User user : userTestList) {
Long userId = user.getUserId();
for (UserMemo userMemo : userMemoTestList) {
if (userId.equals(userMemo.getUserId())) {
String content = userMemo.getContent();
System.out.println("Processing: " + content);
break; // stop inner loop after first match
}
}
}For a more substantial improvement, the author converts the UserMemo list into a Map (keyed by userId ) using Java 8 streams. This changes the lookup from O(n) to O(1) on average, cutting the total runtime to about 1,200 ms.
public static void main(String[] args) {
List
userTestList = getUserTestList();
List
userMemoTestList = getUserMemoTestList();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Map
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("Processing: " + content);
}
}
stopWatch.stop();
System.out.println("Final time: " + stopWatch.getTotalTimeMillis());
}The dramatic speedup is explained by time‑complexity analysis: the original nested loops have O(m × n) complexity, while the map‑based approach reduces it to O(m + n) for building the map plus O(1) per lookup. The author also notes that Java 8’s hash implementation makes collisions rare, so average lookup remains near constant time.
In summary, the article teaches two practical optimization techniques for Java backend code: using break to avoid unnecessary inner‑loop iterations and leveraging a hash‑based Map to replace costly nested scans, both of which significantly improve performance for large data sets.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
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.