Java 迭代字典(Map)的多种方式及其性能对比
在 Java 开发中,Map(字典)是最常用的数据结构之一。遍历 Map 中的键值对是日常编码的高频操作。本文将系统梳理 Java 中迭代 Map 的六种常见方式,通过代码示例说明每种写法的特点,并从性能和可读性角度给出最佳实践建议。
1. 准备工作:定义一个示例 Map
所有示例基于以下 HashMap:
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);
map.put("date", 4);
map.put("elderberry", 5);
下文每段示例代码均可独立运行。
2. 六种迭代方式
2.1 使用 entrySet() + for-each(增强 for 循环)
最经典、推荐度最高的方式。
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " -> " + value);
}
特点:
- 一次迭代同时拿到 key 和 value,无需重复查找。
- 代码简洁,无外部依赖。
- JDK 1.5+ 可用。
2.2 使用 entrySet() + Iterator
在需要迭代过程中删除元素时使用。
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " -> " + value);
// 示例:删除值为偶数的条目
if (value % 2 == 0) {
iterator.remove();
}
}
特点:
- 唯一能在遍历时安全删除元素的方式(通过
iterator.remove())。 - 较 for-each 稍显冗长,但语义明确。
注意: for-each 循环中直接调用
map.remove()会抛出ConcurrentModificationException。
2.3 使用 keySet() + get()(通过键遍历)
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " -> " + value);
}
特点:
- 只关心 key 时的首选。
- 性能缺陷: 每次
map.get(key)都需要在哈希表中重新查找,时间复杂度为 O(1)(平均)但仍有哈希计算和可能的链表/红黑树遍历开销。总耗时约为 entrySet 方式的 1.5~2 倍(数据量越大差距越明显)。
2.4 使用 values() 遍历值
for (Integer value : map.values()) {
System.out.println("value = " + value);
}
特点:
- 当只需要 value、不需要 key 时最简洁。
- 返回的是
Collection<V>视图,直接迭代,性能与 entrySet 相当。
2.5 Java 8 forEach + Lambda
map.forEach((key, value) -> {
System.out.println(key + " -> " + value);
});
特点:
- 语法最简洁,一行完成。
- 内部实现本质是对
entrySet进行迭代(Map#forEach默认实现等价于for (Map.Entry<K, V> entry : entrySet()))。 - 支持 lambda 块内使用外部变量(需为 effectively final)。
- 适合函数式风格、流式处理链。
注意: Lambda 体内不能使用
break/continue,若需要提前退出仍需传统 for 循环。
2.6 Stream API(entrySet().stream())
map.entrySet().stream()
.filter(entry -> entry.getValue() > 2)
.forEach(entry -> System.out.println(entry.getKey() + " -> " + entry.getValue()));
// 更函数式的写法:收集为新的 Map
Map<String, Integer> filtered = map.entrySet().stream()
.filter(entry -> entry.getValue() > 2)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
System.out.println(filtered);
特点:
- 适合链式操作(过滤、映射、排序、收集等)。
- 惰性求值,只有遇到终止操作(如
forEach、collect)才真正遍历。 - 性能上比普通 for-each 多出 stream 封装和 lambda 调用开销,在小数据集中可忽略,但在**超大规模数据(百万级以上)**且无额外操作时,传统 for-each 略快。
- 可并行(
.parallelStream()),在多核 + 大数据场景下可显著提速。
3. 性能对比(定性分析)
| 迭代方式 | 是否同时获得 key/value | 是否支持遍历时删除 | 性能(相对) | 适用场景 |
|---|---|---|---|---|
entrySet + for-each |
✅ | ❌ | ★★★★★ | 常规遍历,首选 |
entrySet + Iterator |
✅ | ✅ | ★★★★★ | 需要遍历时删除 |
keySet + get() |
✅(但 get 有额外开销) | ❌ | ★★★☆☆ | 仅需 key 时;数据量小 |
values() |
❌(只有 value) | ❌ | ★★★★★ | 仅需 value |
forEach + Lambda |
✅ | ❌ | ★★★★★ | 函数式风格,简洁 |
| Stream API | ✅ | ❌ | ★★★★☆ | 过滤/转换/并行等复杂操作 |
关键结论
- 首选
entrySet+ for-each:可读性好,性能最优。 - 避免
keySet+get()遍历:每次 get 是额外查找,数据量大时性能下降明显。 - 需要删除元素时用
Iterator,或使用 Java 8 的removeIf()(map.values().removeIf(...))。 - Java 8
forEach是函数式风格的最佳选择,性能与传统 for-each 相当。 - Stream API 在简单遍历场景下没有性能优势,但在链式操作和并行处理时不可替代。
values()只需 value 时最干净。
4. 最佳实践总结
- 无脑首选:
for (Map.Entry<K, V> e : map.entrySet()) { ... } - 函数式风格:
map.forEach((k, v) -> { ... }) - 需要过滤/转换:
map.entrySet().stream().filter(...).collect(...) - 需要遍历时删除:
Iterator或map.entrySet().removeIf(...)(Java 8+) - 只需 key:
for (K key : map.keySet()) { ... } - 只需 value:
for (V value : map.values()) { ... }
5. 完整可运行示例
将下面代码保存为 IterateMapDemo.java,直接编译运行:
import java.util.*;
public class IterateMapDemo {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);
map.put("date", 4);
map.put("elderberry", 5);
// 1. entrySet + for-each
System.out.println("=== 1. entrySet + for-each ===");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
// 2. entrySet + Iterator(支持删除)
System.out.println("=== 2. entrySet + Iterator ===");
Iterator<Map.Entry<String, Integer>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Integer> entry = iter.next();
if (entry.getValue() % 2 == 0) {
iter.remove(); // 安全删除
} else {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
// 重新填充数据用于后续演示
map.put("banana", 2);
map.put("date", 4);
// 3. keySet + get()
System.out.println("=== 3. keySet + get() ===");
for (String key : map.keySet()) {
System.out.println(key + " -> " + map.get(key));
}
// 4. values()
System.out.println("=== 4. values() ===");
for (Integer value : map.values()) {
System.out.println("value = " + value);
}
// 5. forEach + Lambda
System.out.println("=== 5. forEach + Lambda ===");
map.forEach((key, value) -> System.out.println(key + " -> " + value));
// 6. Stream API
System.out.println("=== 6. Stream API ===");
map.entrySet().stream()
.filter(e -> e.getValue() > 2)
.forEach(e -> System.out.println(e.getKey() + " -> " + e.getValue()));
}
}
6. 小结
| 场景 | 推荐方式 |
|---|---|
| 通用遍历 | entrySet + for-each |
| 函数式风格 | Map.forEach() |
| 过滤/转换 | Stream API |
| 遍历时删除 | Iterator.remove() / removeIf() |
| 只需 key | keySet() |
| 只需 value | values() |
选择最合适的方式,在保证代码可读性的前提下兼顾性能,才是好的工程实践。
Happy Coding! 🚀