今天在做代码开始时,遇到一个这样的场景:
/**
* 过滤掉维度数据中不需要传递的字段
*
* @param dataJsonObj {"is_sale":0,"sku_desc":"小米12S Ultra 骁龙8+旗舰处理器 徕卡光学镜头 2K超视感屏 120Hz高刷 67W快充 12GB+256GB 经典黑 5G手机","tm_id":5,"create_time":"2021-12-14 00:00:00","price":6499,"sku_default_img":"http://47.93.148.192:8080/group1/M00/00/01/rBHu8l-rfvmAIpgZAAIvrX6L9fo612.jpg","weight":1.00,"sku_name":"小米12S Ultra 骁龙8+旗舰处理器 徕卡光学镜头 2K超视感屏 120Hz高刷 67W快充 12GB+256GB 经典黑 5G手机666","id":3,"spu_id":1,"category3_id":61}
* @param sinkColumns id,spu_id,price,sku_name,sku_desc,weight,tm_id,category3_id,sku_default_img,is_sale,create_time
*/
private static void deleteNotNeedColumns(JSONObject dataJsonObj, String sinkColumns) {
List<String> sinkColumnsList = Arrays.asList(sinkColumns.split(","));
Set<Map.Entry<String, Object>> entrySet = dataJsonObj.entrySet();
// 不能在遍历集合的时候,去删除集合中的元素
for (Map.Entry<String, Object> entry : entrySet) {
if (sinkColumnsList.contains(entry.getKey())) {
entrySet.remove(entry);
}
}
}
相信大家对于这种实现思路都可以想到,但是当我们以为我们很聪明的时候,问题就出现了:
后面查找到了原因**(还是自己Java基础不好)**:
在Java中,遍历集合(如ArrayList
、HashSet
等)时直接删除集合中的元素可能会导致ConcurrentModificationException
异常。这是因为大多数Java集合框架中的集合类(除了ConcurrentHashMap
等并发集合)并不是线程安全的,也不是设计为在迭代过程中进行修改的。
这里有几个关键点解释为什么不能在遍历过程中删除元素:
-
内部状态不一致:集合类通常维护一个内部状态(如
modCount
),用于检测集合在迭代过程中是否被修改。如果在迭代过程中修改了集合(如添加或删除元素),modCount
会增加,而迭代器检测到modCount
变化后会抛出ConcurrentModificationException
。 -
迭代器失效:迭代器在创建时依赖于集合的当前状态。如果在迭代过程中修改了集合,迭代器可能会失效,因为它依赖的集合状态已经改变。这可能导致迭代器返回错误的结果或抛出异常。
-
线程安全问题:如果在多线程环境中,多个线程同时修改集合,而没有适当的同步机制,那么集合的状态将变得不可预测,这可能导致
ConcurrentModificationException
或更严重的并发问题。
如何在遍历过程中安全地删除元素?
尽管直接在遍历过程中删除元素是不安全的,但有几种方法可以在遍历集合时安全地删除元素:
-
使用
Iterator
的remove
方法:
使用集合的iterator()
方法获取迭代器,并在遍历过程中使用迭代器的remove()
方法来删除元素。这是安全的方法,因为它会正确地更新迭代器的内部状态。List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d")); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String element = iterator.next(); if (element.equals("b")) { iterator.remove(); } }
-
使用Java 8的
removeIf
方法:
如果你使用的是Java 8或更高版本,可以使用Collection
接口的removeIf
方法,它接受一个Predicate
作为参数,并安全地从集合中删除满足条件的元素。List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d")); list.removeIf(element -> element.equals("b"));
-
使用
for-each
循环结合Iterator
(不推荐,但可行):
虽然直接在for-each
循环中删除元素会导致异常,但你可以结合使用Iterator
在for-each
风格的循环中安全删除元素(实际上这种写法不常见,直接使用Iterator
的循环更清晰)。 -
使用临时集合收集要删除的元素:
先遍历集合,将要删除的元素收集到一个临时集合中,然后再遍历这个临时集合并从原集合中删除这些元素。这种方法虽然可行,但效率较低,且不如使用Iterator
或removeIf
方法直接。
总的来说,使用Iterator
的remove
方法或Java 8的removeIf
方法是遍历集合时安全删除元素的最佳实践。