Boost Java Loop Performance: Replace Nested Loops with a HashMap
This article demonstrates how to dramatically speed up Java code that matches items between two large lists by eliminating nested loops, using early‑exit with break and, more effectively, pre‑building a HashMap for O(1) lookups, with concrete timing results and full code examples.
When you need to find matching IDs between two collections—such as a User list and a UserMemo list—many developers write a nested for loop, which becomes extremely slow as the data size grows.
Example entity classes:
@Data
public class User {
private Long userId;
private String name;
} @Data
public class UserMemo {
private Long userId;
private String content;
}Test data generation creates 50,000 User objects and 30,000 UserMemo objects.
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;
}Naïve nested loop implementation:
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: " + content);
}
}
}This approach iterates 5 × 10⁴ × 3 × 10⁴ ≈ 1.5 billion comparisons, taking about 26,857 ms on the test data.
Adding a break after finding the first match reduces the inner loop work, cutting the time to roughly 10,000 ms .
For a scalable solution, pre‑populate a Map<Long, String> from the UserMemo list and then perform a single lookup per user:
public static void main(String[] args) {
List<User> userTestList = getUserTestList();
List<UserMemo> userMemoTestList = getUserMemoTestList();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// Build map: userId → content
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("Processing content: " + content);
}
}
stopWatch.stop();
System.out.println("Final time: " + stopWatch.getTotalTimeMillis());
}Running this version drops the execution time to just a few hundred milliseconds, because the map provides average‑case O(1) access.
The improvement stems from changing the algorithmic complexity from quadratic ( O(n²)) to linear ( O(n)). A hash‑based map hashes the userId to an array index, yielding near‑constant lookup time; collisions are rare with Java 8’s hash function, and even in the worst case the cost degrades only to O(log n) or O(n) for a single bucket.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
