遍历hashmap时直接修改key会抛concurrentmodificationexception;安全做法是用iterator.remove()删除旧entry后再put新key-value,或先复制map再批量操作。

直接改Map的key会抛ConcurrentModificationException
Java里遍历HashMap时用entrySet()或keySet()迭代,再调put()或remove()改key——几乎必崩。这不是bug,是fail-fast机制在起作用:迭代器检测到结构被并发修改,立刻报ConcurrentModificationException。
真正安全的做法是先收集要改的key,再批量操作:
- 用
new HashMap(originalMap)创建副本,遍历副本改原map(适合小数据) - 用
Iterator<map.entry>></map.entry>+iterator.remove()删旧entry,再put()新key-value(推荐) - 别碰
forEach()+put(),lambda里改map照样触发异常
replaceAll()只改value,且不支持key重映射
replaceAll()是Map接口自带方法,签名是void replaceAll(BiFunction super K, ? super V, ? extends V> function),它只允许你基于当前key和value计算出新value,但key本身不可变。
常见误用场景:
立即学习“Java免费学习笔记(深入)”;
- 想把所有value里的空格替换成下划线,写
map.replaceAll((k, v) -> v.replace(" ", "_"))——这没问题 - 想把key为"oldKey"的条目改成key="newKey",试图写
(k, v) -> k.equals("oldKey") ? "newKey" : k——无效,返回值只会当新value用 - 如果value是
null,replaceAll()会传null进去,函数里得自己判空,否则NPE
compute()和computeIfPresent()才是改key的正解
真要动态生成新key(比如把所有key转小写、加前缀、按规则替换),必须分两步:删旧、插新。而computeIfPresent()能帮你原子化地处理“存在才改”的逻辑,compute()则覆盖null key也处理。
实操建议:
- 改单个key:用
map.compute("oldKey", (k, v) -> v == null ? null : new Pair("newKey", v))不行——compute不能换key,只能换value - 正确做法:先
map.remove("oldKey")拿到旧value,再map.put("newKey", oldValue) - 批量改key:用
entrySet().stream().collect(Collectors.toMap(...))构造新map,再map.clear(); map.putAll(newMap) -
compute()在高并发下有锁开销,简单遍历+重建map反而更快,别迷信“函数式就高级”
String替换类操作别漏掉replace()和replaceAll()的区别
很多人一看到“替换”就条件反射用replaceAll(),但在Map value处理中,90%场景其实该用replace()。
关键区别:
-
replace("a", "b")是字面量替换,快且安全 -
replaceAll("a", "b")把第一个参数当正则,如果传了"."、"\d"之类没转义,结果完全不对 - value是
String时,做路径/URL/JSON字段替换,优先选replace();只有明确需要正则能力(比如替换所有数字串)才用replaceAll() - 注意
replace(CharSequence, CharSequence)和replace(char, char)性能差一个数量级,大数据量慎用后者
改Map不是写诗,能用remove()+put()说清的事,就别硬套compute();正则能不用就不用,replace()多香。










