在Java中,安全删除Map中过期项应使用entrySet().iterator()并调用iter.remove();keySet()或values()迭代器不支持remove()操作,会抛出UnsupportedOperationException。

在 Java 中,直接用 Iterator 遍历 Map 并调用 remove() 是安全的,但必须使用 entrySet().iterator(),不能用 keySet() 或 values() 的迭代器去删整个键值对——因为它们不支持 remove() 操作(会抛 UnsupportedOperationException)。
用 entrySet().iterator() 安全遍历并删除过期项
这是最标准、线程不安全但单线程下绝对安全的方式。核心是:获取 Map.entrySet().iterator(),检查每个 Entry 的值(或键)是否过期,满足条件则调用 iterator.remove()。
示例(基于时间戳判断缓存是否过期):
Map<string cacheitem> cache = new HashMap();
long now = System.currentTimeMillis();
Iterator<map.entry cacheitem>> iter = cache.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<string cacheitem> entry = iter.next();
CacheItem item = entry.getValue();
if (item.isExpired(now)) {
iter.remove(); // ✅ 安全:entrySet 迭代器支持 remove
}
}</string></map.entry></string>
避免 ConcurrentModificationException 的关键点
以下操作都会导致异常,务必避开:
- 在 for-each 循环中调用
cache.remove(key)—— 底层仍是增强 for,本质是 iterator,但没暴露 remove 方法; - 用
keySet().iterator()然后调用iter.remove()—— 它只删 key,Map 结构可能不一致,且某些实现(如ConcurrentHashMap)不支持; - 边遍历边用
cache.clear()或cache.putAll(...)—— 修改结构触发 fail-fast。
多线程场景:优先考虑 ConcurrentHashMap + computeIfPresent
如果缓存被多个线程读写,别自己加锁遍历。推荐用 ConcurrentHashMap,配合原子操作清理:
-
computeIfPresent(key, (k, v) -> v.isExpired(now) ? null : v):对指定 key 原子判断并移除; - 批量清理可用
replaceAll((k, v) -> v.isExpired(now) ? null : v)(JDK 8+),它内部按段处理,不会抛并发异常; - 更彻底的清理可结合
keySet().removeIf(k -> cache.get(k).isExpired(now))(注意:ConcurrentHashMap.keySet().removeIf是线程安全的)。
进阶建议:封装成自动过期的缓存容器
实际项目中,不建议每次手动迭代清理。可基于 LinkedHashMap 重写 removeEldestEntry() 实现 LRU,或用成熟库:
-
Caffeine:支持写入后过期(
expireAfterWrite)、访问后过期(expireAfterAccess),自动异步清理; -
Guava Cache:类似,提供
refreshAfterWrite和监听器; - 自研轻量级缓存时,可在
get()中检查过期并主动remove(),避免集中遍历压力。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











