1. 概述
Java 中的 Map
是一种键值对存储结构,支持通过键进行高效查找、更新和删除操作。掌握 Map
的遍历方法对很多编程任务至关重要。
本文将深入探讨 Java 中遍历 Map
的多种方式,并通过基准测试对比性能差异,帮你找到最适合业务场景的方案。
简单来说,我们可以通过 entrySet()
、keySet()
或 values()
方法获取 Map
内容。由于这些方法都返回集合,遍历原理基本相通。
2. 快速了解 Map 的核心方法
在深入遍历技术前,先复习三个访问 Map
内容的关键方法:
✅ entrySet()
返回 Map.Entry
类型的 Set
视图。通过 entry.getKey()
获取键,entry.getValue()
获取值。
✅ keySet()
返回包含所有键的 Set
集合。
✅ values()
返回包含所有值的 Collection
(注意不是 Set
)。
3. 使用 for-each + entrySet()
最经典的遍历方式:通过 entrySet()
结合增强 for 循环:
long iterateUsingEnhancedForLoopAndEntrySet(Map<Integer, Integer> map) {
long sum = 0;
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
sum += entry.getValue();
}
return sum;
}
这里先提取 Map
的条目集合,然后用传统 for-each 遍历并累加值。
4. 使用 for-each + keySet()
当需要先获取键再访问值时,keySet()
是备选方案:
long iterateUsingKeySetAndEnhanceForLoop(Map<Integer, Integer> map) {
long sum = 0;
for (Integer key : map.keySet()) {
sum += map.get(key);
}
return sum;
}
⚠️ 注意:这种方式通过键二次获取值,性能稍差(后文有数据验证)。
5. 使用 for-each + values()
当只关心值不关心键时,values()
是最直接的选择:
long iterateValuesUsingValuesMethodAndEnhanceForLoop(Map<Integer, Integer> map) {
long sum = 0;
for (Integer value : map.values()) {
sum += value;
}
return sum;
}
直接遍历值集合,完全跳过键的操作。
6. 使用 Iterator + entrySet()
传统迭代器方式遍历 entrySet()
:
long iterateUsingIteratorAndEntrySet(Map<Integer, Integer> map) {
long sum = 0;
Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, Integer> pair = iterator.next();
sum += pair.getValue();
}
return sum;
}
适合需要在遍历中删除元素的场景(通过 iterator.remove()
)。
7. 使用 Iterator + keySet()
同样可以用迭代器遍历 keySet()
:
long iterateUsingIteratorAndKeySet(Map<Integer, Integer> map) {
long sum = 0;
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
sum += map.get(key);
}
return sum;
}
和 for-each 版本一样,存在二次取值开销。
8. 使用 Iterator + values()
迭代器遍历值集合:
long iterateUsingIteratorAndValues(Map<Integer, Integer> map) {
long sum = 0;
Iterator iterator = map.values().iterator();
while (iterator.hasNext()) {
Integer value = iterator.next();
sum += value;
}
return sum;
}
纯值遍历场景的迭代器实现。
9. 使用 forEach + Lambda
Java 8 引入的 forEach()
配合 Lambda 表达式:
void iterateUsingLambdaAndForEach(Map<Integer, Integer> map) {
map.forEach((k, v) -> LOGGER.info("Key: {}, Value: {}", k, v));
}
✅ 适用场景:对每个条目执行独立操作(如日志记录)。
❌ 不推荐场景:聚合操作(如求和):
long iterateValuesUsingLambdaAndForEach(Map<Integer, Integer> map) {
AtomicLong sum = new AtomicLong(0);
map.values().forEach(v -> sum.addAndGet(v));
return sum.get();
}
聚合操作建议用 Stream API(后文详述)。
10. 使用 Stream API
Java 8 的 Stream API 为数据处理提供强大能力:
long iterateUsingStreamAPIAndEntrySet(Map<Integer, Integer> map) {
return map.entrySet()
.stream()
.mapToLong(Map.Entry::getValue)
.sum();
}
同样支持 keySet()
版本:
long iterateUsingStreamAPIAndKeySet(Map<Integer, Integer> map) {
return map.keySet()
.stream()
.mapToLong(map::get)
.sum();
}
并行处理场景用 parallelStream()
:
long iterateMapUsingParallelStreamApi(Map<Integer, Integer> map) throws IOException {
return map.entrySet()
.parallelStream()
.mapToLong(Map.Entry::getValue)
.sum();
}
⚠️ 注意:并行流有额外开销,小数据量时可能适得其反。
11. 遍历 Eclipse Collections 的 MutableMap
Eclipse Collections 提供高性能集合实现:
long iterateEclipseMap(MutableMap<Integer, Integer> mutableMap) throws IOException {
AtomicLong sum = new AtomicLong(0);
mutableMap.forEachKeyValue((key, value) -> {
sum.addAndGet(value);
});
return sum.get();
}
通过 forEachKeyValue()
直接操作键值对。
12. 遍历 Apache Commons 的 IterableMap
Apache Commons Collections 的 IterableMap
使用专用迭代器:
long iterateUsingMapIteratorApacheCollection(IterableMap<Integer, Integer> map) {
long sum = 0;
MapIterator<Integer, Integer> iterate = map.mapIterator();
while (iterate.hasNext()) {
iterate.next();
sum += iterate.getValue();
}
return sum;
}
MapIterator
支持直接获取键值,无需二次查找。
13. 性能对比
使用 JMH 进行基准测试:
13.1 测试环境配置
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 10, timeUnit = TimeUnit.MICROSECONDS)
@Measurement(iterations = 5, time = 10, timeUnit = TimeUnit.MICROSECONDS)
public class MapIterationBenchmark {
@Param({"100","1000","10000","100000"})
public int size;
MapIteration mapIteration = new MapIteration();
Map<Integer, Integer> map;
IterableMap<Integer, Integer> iterableMap;
MutableMap<Integer, Integer> mutableMap;
// ...
}
初始化方法:
@Setup(Level.Trial)
public void setup() {
map = new HashMap<>();
iterableMap = new HashedMap<>();
mutableMap = UnifiedMap.newMap();
for (int i = 0; i < size; i++) {
map.put(i, i);
iterableMap.put(i, i);
mutableMap.put(i, i);
}
}
13.2 测试结果(10万元素)
方法 | 平均耗时 (秒) | 误差范围 |
---|---|---|
parallelStream() |
0.001 | ±0.001 |
Iterator+entrySet() |
0.002 | ±0.002 |
Stream+entrySet() |
0.002 | ±0.001 |
forEach+Lambda |
0.002 | ±0.001 |
for-each+entrySet() |
0.003 | ±0.005 |
for-each+values() |
0.003 | ±0.004 |
for-each+keySet() |
0.003 | ±0.003 |
Eclipse Collections | 0.003 | ±0.003 |
Iterator+keySet() |
0.004 | ±0.003 |
Apache Commons | 0.004 | ±0.010 |
关键发现:
- ✅ 并行流 (
parallelStream
) 在大数据量时表现最佳 - ✅ **
entrySet()
始终优于keySet()
**(避免二次取值) - ❌ Apache Commons 迭代器性能最差且误差最大
- ⚠️ 误差率较高:测试结果需谨慎解读
14. 总结
本文系统梳理了 Java 中遍历 Map
的 12 种方法,核心结论如下:
- 首选方案:
entrySet()
+ for-each(代码简洁且高效) - 聚合操作:优先使用 Stream API(链式调用更优雅)
- 大数据量:考虑
parallelStream()
(注意线程安全) - 特殊场景:
- 需要删除元素 → 用
Iterator
- 只关心值 → 用
values()
- 使用第三方库 → 优先 Eclipse Collections
- 需要删除元素 → 用
💡 踩坑提示:避免在循环中用
keySet()
+map.get()
,性能损失可达 20%!
选择遍历方式时,优先考虑代码可读性,性能瓶颈出现时再针对性优化。