关于集合遍历并删除报错详解
程序员文章站
2022-04-06 15:33:36
...
一、list集合正确删除
list集合删除不要使用增强for,建议使用for(int i;;)这种方法,注意这种方法删除集合元素会导致索引前移导致遍历问题
例如:
private static void delFor() {
List<String> blist = new ArrayList<>();
blist.add("a");
blist.add("b");
blist.add("c");
blist.add("d");
blist.add("e");
blist.add("f");
for (int i = 0; i < blist.size(); i++) {
if(blist.get(i).equals("b")) {
blist.remove(blist.get(i));
//这里输出被删除的元素有问题,主要是索引改变,删除本身没问题
System.out.println("删除的元素是: " + blist.get(i));
}
}
System.out.println(blist);
}
二、增强for删除
增强for遍历等效于使用迭代器,删除会报ConcurrentModificationException
private static void delNormal() {
List<String> alist = new ArrayList<>();
alist.add("1");
alist.add("2");
alist.add("3");
for (String item : alist) {
if (item.equals("2")) { //删除2不报错
alist.remove(item);
System.out.println("被删除的元素"+item);
}
}
System.out.println("遍历删除后的集合" + " " + alist);
}
结果:被删除的元素2
遍历删除后的集合 [1, 3]
上面结论大家肯定知道,但是看这段代码,删除成功了,并没有报错,结论不对吗?
再看一段代码:
private static void del() {
List<String> blist = new ArrayList<>();
blist.add("a");
blist.add("b");
blist.add("c");
blist.add("d");
blist.add("e");
blist.add("f");
for (String item : blist) {
if(item.equals("b")) {
blist.remove(item);
System.out.println("删除的元素是: "+ item );
}
}
System.out.println("删除后的集合为:" +blist);
}
这段代码运行直接报错,是上面描述的异常
private static void delForIterator() {
List<String> blist = new ArrayList<>();
blist.add("a");
blist.add("b");
blist.add("c");
blist.add("d");
blist.add("e");
blist.add("f");
Iterator it = blist.iterator();
while(it.hasNext()) {
String item = (String) it.next();
if(item.equals("b")) {
it.remove();
System.out.println("删除的元素是: "+ item );
}
}
System.out.println("删除后的集合为:" +blist);
}
正确删除,没问题
我们看下迭代删除的源码:
private class Itr implements Iterator<E> {
int cursor; // /将要访问的元素的索引
int lastRet = -1; // 上一个访问元素的索引
int expectedModCount = modCount;//expectedModCount为预期修改值,初始化等于modCount(AbstractList类中的一个成员变量)
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//每次调用next()需要check
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
//重新给expectedModCount值相等
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
在获取一个Iterator对象时,会初始化成员变量
- cursor(0)
- lastRet(-1)
- expectedModCount(ModCount=初始集合长度).
对于第一段代码(增强for底层还是调用迭代器),不报错是因为在删除2以后,调用hasNext()方法,cursor值移动至2,size此时变成2,相等,跳出循环,所以没有报错,这仅仅是个巧合而已。
增加for删除报错的主要原因是每次调用next()方法,都会检查expectedModCount和
ModCount值是否相等,当我们删除元素后,ModCount会改变与expectedModCount值不同,引起报错。
使用iterator删除时,看上面源码,会再次赋值它们相等,所以不会报错。
三、多线程情况下的集合删除
使用迭代器的iterator.remove()在单线程下是不会报错的,但是在多线程情况下,一个线程修改了集合的modCount导致另外一个线程迭代时modCount与该迭代器的expectedModCount不相等,这也会报异常。
public class RemoveListForThreads implements Runnable {
static List<String> alist = new ArrayList<>();
public static void main(String[] args) {
RemoveListForThreads s = new RemoveListForThreads();
alist.add("a");
alist.add("b");
alist.add("c");
alist.add("d");
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1,
TimeUnit.SECONDS, workQueue);
for (int i = 0; i < 5; i++) {
executor.execute(s);
}
executor.shutdown();
}
@Override
public synchronized void run() {
Iterator<String> iterator = alist.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
if (s.equals("c") && Thread.currentThread().getName().equals("pool-1-thread-1")) {
iterator.remove();
System.out.println(Thread.currentThread().getName() + " " + s);
}
System.out.println(Thread.currentThread().getName() + " " + s);
}
System.out.println(alist);
}
}
上面这段代码创建了一个线程池,开启了五个线程,在run方法中遍历并删除“b”元素,如果该方法不加同步锁sychronized,也会抛出异常
推荐阅读