Java如何在List或Map遍历过程中删除元素

遍历删除List或Map中的元素有很多种方法,当运用不当的时候就会产生问题。下面通过这篇文章来再学习学习吧。

一、List遍历过程中删除元素

使用索引下标遍历的方式

示例:删除列表中的2

public static void main(String[] args) {
  List<Integer> list = new ArrayList<Integer>();
  list.add(1);
  list.add(2);
  list.add(2);
  list.add(3);
  list.add(4);

  for (int i = 0; i < list.size(); i++) {
   if(2 == list.get(i)){
    list.remove(i);
   }
   System.out.println(list.get(i));
  }

  System.out.println("list=" + list.toString());

 }

输出结果:

1
2
3
4
list=[1, 2, 3, 4]

问题:

结果显示只删除了一个2,另一个2被遗漏了,原因是:删除了第一个2后,集合里的元素个数减1,后面的元素往前移了1位,导致了第二个2被遗漏了。

使用For循环遍历的方式

示例:

public static void listIterator2(){
  List<Integer> list = new ArrayList<Integer>();
  list.add(1);
  list.add(2);
  list.add(2);
  list.add(3);
  list.add(4);

  for (int value : list) {
   if(2 == value){
    list.remove(value);
   }
   System.out.println(value);
  }

  System.out.println("list=" + list.toString());

 }

结果:

Exception in thread "main" 1
2
java.util.ConcurrentModificationException
 at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
 at java.util.ArrayList$Itr.next(Unknown Source)
 at test.ListIterator.listIterator2(ListIterator.java:39)
 at test.ListIterator.main(ListIterator.java:10)

说明:

jdk中对ConcurrentModificationException的描述:

public class ConcurrentModificationException extends

RuntimeException当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。

例如,某个线程在 Collection 上进行迭代时,通常不允许另一个线性修改该 Collection。通常在这些情况下,迭代的结果是不确定的。如果检测到这种行为,一些迭代器实现(包括 JRE 提供的所有通用 collection 实现)可能选择抛出此异常。执行该操作的迭代器称为快速失败 迭代器,因为迭代器很快就完全失败,而不会冒着在将来某个时间任意发生不确定行为的风险。

注意:此异常不会始终指出对象已经由不同 线程并发修改。如果单线程发出违反对象协定的方法调用序列,则该对象可能抛出此异常。例如,如果线程使用快速失败迭代器在 collection 上迭代时直接修改该 collection,则迭代器将抛出此异常。

注意:迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败操作会尽最大努力抛出 ConcurrentModificationException。因此,为提高此类操作的正确性而编写一个依赖于此异常的程序是错误的做法,正确做法是:ConcurrentModificationException 应该仅用于检测 bug。

Java中的For each实际上使用的是iterator进行处理的。而iterator是不允许集合在iterator使用期间删除的。所以导致了iterator抛出了ConcurrentModificationException

正确的方式

示例:

public static void listIterator3(){
  List<Integer> list = new ArrayList<Integer>();
  list.add(1);
  list.add(2);
  list.add(2);
  list.add(3);
  list.add(4);

  Iterator<Integer> it = list.iterator();
  while (it.hasNext()){
   Integer value = it.next();
   if (2 == value) {
    it.remove();
   }

   System.out.println(value);
  }

  System.out.println("list=" + list.toString());
 }

结果:

1
2
2
3
4
list=[1, 3, 4]

二、Map遍历过程中删除元素

正确做法的示例:

public static void main(String[] args) {
 HashMap<String, String> map = new HashMap<String, String>();
 map.put("1", "test1");
 map.put("2", "test2");
 map.put("3", "test3");
 map.put("4", "test4");

 //完整遍历Map
 for (Entry<String, String> entry : map.entrySet()) {
  System.out.printf("key: %s value:%s\r\n", entry.getKey(), entry.getValue());
 }

 //删除元素
 Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
 while(it.hasNext())
 {
  Map.Entry<String, String> entry= it.next();
  String key= entry.getKey();
  int k = Integer.parseInt(key);
  if(k%2==1)
  {
   System.out.printf("delete key:%s value:%s\r\n", key, entry.getValue());
   it.remove();
  }
 } 

 //完整遍历Map
 for (Entry<String, String> entry : map.entrySet()) {
  System.out.printf("key: %s value:%s\r\n", entry.getKey(), entry.getValue());
 }
}

结果:

key: 1 value:test1
key: 2 value:test2
key: 3 value:test3
key: 4 value:test4
delete key:1 value:test1
delete key:3 value:test3
key: 2 value:test2
key: 4 value:test4

注意

但对于iterator的remove()方法,也有需要我们注意的地方:

每调用一次iterator.next()方法,只能调用一次remove()方法。

调用remove()方法前,必须调用过一次next()方法。

JDK-API中对于remove()方法的描述:

void remove()从迭代器指向的集合中移除迭代器返回的最后一个元素(可选操作)。每次调用 next 只能调用一次此方法。如果进行迭代时用调用此方法之外的其他方式修改了该迭代器所指向的集合,则迭代器的行为是不明确的。

抛出:UnsupportedOperationException - 如果迭代器不支持 remove 操作。IllegalStateException - 如果尚未调用 next 方法,或者在上一次调用 next 方法之后已经调用了remove 方法。

总结

以上就是关于List与Map的遍历过程中删除元素的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

时间: 2016-12-03

java list,set,map,数组间的相互转换详解

java list,set,map,数组间的相互转换详解 1.list转set Set set = new HashSet( new ArrayList()); 2.set转list List list = new ArrayList( new HashSet());  3.数组转为list List stooges = Arrays.asList( "Larry" , "Moe" , "Curly" ); 此时stooges中有有三个元素.注意

详解 Spring注解的(List&Map)特殊注入功能

详解 Spring注解的(List&Map)特殊注入功能 最近接手一个新项目,已经没有原开发人员维护了.项目框架是基于spring boot进行开发.其中有两处Spring的注解花费了大量的时间才弄明白到底是怎么用的,这也涉及到spring注解的一个特殊的注入功能. 首先,看到代码中有直接注入一个List和一个Map的.示例代码如下: @Autowired private List<DemoService> demoServices; @Autowired private Map<

jstl之map,list访问遍历以及el表达式map取值的实现

场景: request域里的数据为Map<Role, Map<String, List<Menu>>>,Role为枚举类型,为用户的Role那么访问遍历如下: <c:set var="user" value="${session_usr_key}" /> <c:forEach items="${roleMenuMap[user.role]}" var="entry">

详解javaweb中jstl如何循环List中的Map数据

详解javaweb中jstl如何循环List中的Map数据 第一种方式: 1:后台代码(测试) List<Map<String, Object>> list = new ArrayList<Map<String,Object>>(); Map<String, Object> map = null; for (int i = 0; i < 4; i++) { map = new HashMap<String, Object>();

Struts2中ognl遍历数组,list和map方法详解

一.简介 <s:iterator />可以遍历数据栈里面的任何数组,集合等等 在使用这个标签的时候有三个属性值得我们关注 1. value属性:可选的属性,value属性是指一个被迭代的集合,使用ognl表达式指定,如果为空的话默认就是ValueStack栈顶的集合. 2.id属性:可选属性, 是指集合元素的id 3.status属性:可选属性,该属性在迭代时会产生一个IteratorStatus对象,该对象可以判断当前元素的位置,包含了以下属性方法: int getCount(); 迭代元素

详解Java中list,set,map的遍历与增强for循环

详解Java中list,set,map的遍历与增强for循环 Java集合类可分为三大块,分别是从Collection接口延伸出的List.Set和以键值对形式作存储的Map类型集合. 关于增强for循环,需要注意的是,使用增强for循环无法访问数组下标值,对于集合的遍历其内部采用的也是Iterator的相关方法.如果只做简单遍历读取,增强for循环确实减轻不少的代码量. 集合概念: 1.作用:用于存放对象 2.相当于一个容器,里面包含着一组对象,其中的每个对象作为集合的一个元素出现 3.jav

java 三种将list转换为map的方法详解

java 三种将list转换为map的方法详解 在本文中,介绍三种将list转换为map的方法: 1) 传统方法 假设有某个类如下 class Movie { private Integer rank; private String description; public Movie(Integer rank, String description) { super(); this.rank = rank; this.description = description; } public Int

Java concurrency集合之ConcurrentSkipListMap_动力节点Java学院整理

ConcurrentSkipListMap介绍 ConcurrentSkipListMap是线程安全的有序的哈希表,适用于高并发的场景. ConcurrentSkipListMap和TreeMap,它们虽然都是有序的哈希表.但是,第一,它们的线程安全机制不同,TreeMap是非线程安全的,而ConcurrentSkipListMap是线程安全的.第二,ConcurrentSkipListMap是通过跳表实现的,而TreeMap是通过红黑树实现的. 关于跳表(Skip List),它是平衡树的一种

Java concurrency集合之ConcurrentHashMap_动力节点Java学院整理

ConcurrentHashMap介绍 ConcurrentHashMap是线程安全的哈希表.HashMap, Hashtable, ConcurrentHashMap之间的关联如下: HashMap是非线程安全的哈希表,常用于单线程程序中. Hashtable是线程安全的哈希表,它是通过synchronized来保证线程安全的:即,多线程通过同一个"对象的同步锁"来实现并发控制.Hashtable在线程竞争激烈时,效率比较低(此时建议使用ConcurrentHashMap)!因为当一

Java concurrency集合之LinkedBlockingDeque_动力节点Java学院整理

LinkedBlockingDeque介绍 LinkedBlockingDeque是双向链表实现的双向并发阻塞队列.该阻塞队列同时支持FIFO和FILO两种操作方式,即可以从队列的头和尾同时操作(插入/删除):并且,该阻塞队列是支持线程安全. 此外,LinkedBlockingDeque还是可选容量的(防止过度膨胀),即可以指定队列的容量.如果不指定,默认容量大小等于Integer.MAX_VALUE. LinkedBlockingDeque原理和数据结构 LinkedBlockingDeque

Java concurrency集合之CopyOnWriteArraySet_动力节点Java学院整理

CopyOnWriteArraySet介绍 它是线程安全的无序的集合,可以将它理解成线程安全的HashSet.有意思的是,CopyOnWriteArraySet和HashSet虽然都继承于共同的父类AbstractSet:但是,HashSet是通过"散列表(HashMap)"实现的,而CopyOnWriteArraySet则是通过"动态数组(CopyOnWriteArrayList)"实现的,并不是散列表. 和CopyOnWriteArrayList类似,CopyO

Java concurrency集合之ConcurrentLinkedQueue_动力节点Java学院整理

ConcurrentLinkedQueue介绍 ConcurrentLinkedQueue是线程安全的队列,它适用于"高并发"的场景. 它是一个基于链接节点的无界线程安全队列,按照 FIFO(先进先出)原则对元素进行排序.队列元素中不可以放置null元素(内部实现的特殊节点除外). ConcurrentLinkedQueue原理和数据结构 ConcurrentLinkedQueue的数据结构,如下图所示: 说明: 1. ConcurrentLinkedQueue继承于AbstractQ

Java concurrency集合之ConcurrentSkipListSet_动力节点Java学院整理

ConcurrentSkipListSet介绍 ConcurrentSkipListSet是线程安全的有序的集合,适用于高并发的场景. ConcurrentSkipListSet和TreeSet,它们虽然都是有序的集合.但是,第一,它们的线程安全机制不同,TreeSet是非线程安全的,而ConcurrentSkipListSet是线程安全的.第二,ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,而TreeSet是通过TreeMap实现的. Con

Java concurrency集合之ArrayBlockingQueue_动力节点Java学院整理

ArrayBlockingQueue介绍 ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列. 线程安全是指,ArrayBlockingQueue内部通过"互斥锁"保护竞争资源,实现了多线程对竞争资源的互斥访问.而有界,则是指ArrayBlockingQueue对应的数组是有界限的. 阻塞队列,是指多线程访问竞争资源时,当竞争资源已被某线程获取时,其它要获取该资源的线程需要阻塞等待:而且,ArrayBlockingQueue是按 FIFO(先进先出)原则对元素进行

Java concurrency集合之 CopyOnWriteArrayList_动力节点Java学院整理

CopyOnWriteArrayList介绍 它相当于线程安全的ArrayList.和ArrayList一样,它是个可变数组:但是和ArrayList不同的时,它具有以下特性: 1. 它最适合于具有以下特征的应用程序:List 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突. 2. 它是线程安全的. 3. 因为通常需要复制整个基础数组,所以可变操作(add().set() 和 remove() 等等)的开销很大. 4. 迭代器支持hasNext(), next()等不可

Java concurrency之锁_动力节点Java学院整理

根据锁的添加到Java中的时间,Java中的锁,可以分为"同步锁"和"JUC包中的锁". 同步锁 即通过synchronized关键字来进行同步,实现对竞争资源的互斥访问的锁.Java 1.0版本中就已经支持同步锁了. 同步锁的原理是,对于每一个对象,有且仅有一个同步锁:不同的线程能共同访问该同步锁.但是,在同一个时间点,该同步锁能且只能被一个线程获取到.这样,获取到同步锁的线程就能进行CPU调度,从而在CPU上执行:而没有获取到同步锁的线程,必须进行等待,直到获取

Java中StringBuffer和StringBuilder_动力节点Java学院整理

下面先给大家介绍下String.StringBuffer.StringBuilder区别,具体详情如下所示: StringBuffer.StringBuilder和String一样,也用来代表字符串.String类是不可变类,任何对String的改变都 会引发新的String对象的生成:StringBuffer则是可变类,任何对它所指代的字符串的改变都不会产生新的对象.既然可变和不可变都有了,为何还有一个StringBuilder呢?相信初期的你,在进行append时,一般都会选择StringB