entrySet()遍历最常用最安全,能同时获取key和value、避免重复查表;keySet()适合仅需key或value较大时;values()适用于只处理value;需删除元素时必须用Iterator.remove()。

用 entrySet() 遍历最常用也最安全
绝大多数场景下,直接遍历 entrySet() 是首选——它同时拿到 key 和 value,避免了重复查表,性能好,语义也清楚。
常见错误是写成 map.keySet().forEach(key -> map.get(key)):看似简洁,但对 HashMap 还好,换成 TreeMap 或自定义 Map 实现时,get() 可能有额外开销;更严重的是,如果遍历时有其他线程修改了 map(比如删掉当前 key),get() 可能返回 null 或抛 ConcurrentModificationException(取决于具体实现)。
实操建议:
- 用
for (Map.Entry<k v> entry : map.entrySet())</k>显式迭代,控制力强,兼容所有 JDK 版本 - Java 8+ 可用
map.entrySet().forEach(entry -> { ... }),但注意 lambda 内部不能修改 map 结构 - 别在循环体里调用
entry.setValue(...)以外的修改操作(如map.remove(entry.getKey())),会破坏迭代器状态
用 keySet() 遍历只取 key 或只需 key 查 value
当你明确只需要 key,或者 key 足够轻量、value 很大且不总要访问时,keySet() 更省内存——它返回的是 key 的视图,不打包 value。
立即学习“Java免费学习笔记(深入)”;
但要注意:每次用 map.get(key) 都是一次哈希查找。如果 value 是复杂对象,又频繁访问,不如直接用 entrySet() 一次取全。
典型误用:
- 在 for-each 循环里反复写
if (map.get(k) != null && map.get(k).equals("xxx"))——get()被调了两次 - 遍历
keySet()同时又调用map.remove(key):多数Map实现不支持边遍历边删 key(ConcurrentHashMap除外),会抛异常
用 values() 遍历只关心 value 本身
当完全不需要 key,只做统计、过滤或批量处理 value 时,values() 最直白。它返回的是 value 的集合视图,修改它可能间接影响原 map(比如 HashMap.values() 的 remove() 会删对应 key)。
容易踩的坑:
-
values()不保证顺序,哪怕原 map 是LinkedHashMap,它的values()也是按插入顺序,但代码里看不出这个隐含依赖,可读性差 - 对
ConcurrentHashMap,values()返回的是弱一致快照,遍历时其他线程的更新可能不可见 - 别假设
values()可以转成数组后安全修改——比如map.values().toArray()[0] = new Object(),这不会影响 map,只是改了副本
用迭代器手动遍历(Iterator)应对删除需求
唯一安全地在遍历中删除元素的方式,是用 Iterator 的 remove() 方法。用 for-each 或增强 for 循环调 map.remove() 必崩。
示例场景:清理过期缓存项。
Iterator<Map.Entry<String, CacheItem>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, CacheItem> entry = it.next();
if (entry.getValue().isExpired()) {
it.remove(); // 安全,底层调用的是 map 的 remove
}
}
关键点:
-
it.remove()只能调一次,连续调两次会抛IllegalStateException - 不能在迭代器外调
map.remove(),否则下一次it.next()极大概率抛ConcurrentModificationException -
ConcurrentHashMap的迭代器允许并发修改,但它的remove()行为和普通HashMap不同(比如不保证立即从所有线程可见),这点常被忽略










