集合删除元素及异常ConcurrentModificationException
使用普通for循环正序遍历删除list元素, 会跳过某些数据
public class Demo15 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
for (int i = 0; i < list.size(); i++) {
System.out.println("i=" + i);
System.out.println(list.get(i));
if ("b".equals(list.get(i))){
list.remove(i);
}
}
}
}
分析:
当删除了元素后, 有些元素遍历不到, 当i=1的时候, 删除b元素, 然后i++所以i变为2, 但是由于b元素删除掉了, 所以c元素的索引变为1, 而d元素的所以变为2, 所以就跳过了c元素
解决方案:
1.当进行删除成功的时候, 再把i–, 类似于迭代器中的指针前移操作
2.使用逆序遍历删除, 如下代码
for (int i = list.size() - 1; i >= 0; i--) {
System.out.println("i=" + i);
System.out.println(list.get(i));
if ("b".equals(list.get(i))){
list.remove(i);
}
}
3.使用CopyOnWriteArrayList包装原来的集合后, 使用增强for删除, 不要使用普通for
list = new CopyOnWriteArrayList<>(list);
for (String str : list) {
System.out.println(str);
if ("b".equals(str)) {
list.remove(str);
}
}
4.使用迭代器删除
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
System.out.println(str);
if ("b".equals(str)) {
iterator.remove();
}
}
这里引出了迭代器, 但注意迭代器容易发生抛异常ConcurrentModificationException的情况, 以下几种会抛异常的场景
场景1, ArrayList迭代器遍历, 但使用集合的remove方法, 发生ConcurrentModificationException
public class Demo15 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iterator = list.iterator();
list.remove(0);
iterator.next();
}
}
分析:
为什么使用iterator的遍历, 然后使用ArrayList集合的remove方法会抛异常?
查看ArrayList获取迭代器的源码
public Iterator<E> iterator() {
return new ArrayList.Itr();
}
每调用一次, 返回一个新的ArrayList.Itr()对象, 这是一个内部类, 查看内部类Itr构造方法代码
private class Itr implements Iterator<E> {
...
Itr() {
this.expectedModCount = ArrayList.this.modCount;
}
...
}
ArrayList成员变量modCount, 为修改次数
内部类Itr成员变量expectedModCount为期望修改次数
构造函数中, 使用ArrayList的modCount值初始化了expectedModCount
查看iterator的remove方法, 有一步checkForComodification()是检查修改次数是否相等的, 要是检查通过了, 则删除集合的一个元素, 并且将expectedModCount同步回modCount, 保持一致
public void remove() {
if (this.lastRet < 0) {
throw new IllegalStateException();
} else {
this.checkForComodification();
try {
ArrayList.this.remove(this.lastRet);
this.cursor = this.lastRet;
this.lastRet = -1;
this.expectedModCount = ArrayList.this.modCount;
} catch (IndexOutOfBoundsException var2) {
throw new ConcurrentModificationException();
}
}
}
查看checkForComodification方法, modCount和expectedModCount不相等就抛异常了
final void checkForComodification() {
if (ArrayList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
}
查看ArrayList集合的remove方法
public E remove(int index) {
Objects.checkIndex(index, this.size);
Object[] es = this.elementData;
E oldValue = es[index];
this.fastRemove(es, index);
return oldValue;
}
继续查看集合的fastRemove方法, 可以看出对modCount进行++
private void fastRemove(Object[] es, int i) {
++this.modCount;
int newSize;
if ((newSize = this.size - 1) > i) {
System.arraycopy(es, i + 1, es, i, newSize - i);
}
es[this.size = newSize] = null;
}
结论:
调用ArrayList的remove会对modCount加1导致modCount和初始的expectedModCount不一致, 在checkForComodification检查的时候, 不通过, 抛异常, 其实iterator的next方法也调用, 所以实际上是调用iterator的next方法时检查不过, 抛出的异常
public E next() {
this.checkForComodification();
int i = this.cursor;
if (i >= ArrayList.this.size) {
throw new NoSuchElementException();
} else {
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
} else {
this.cursor = i + 1;
return elementData[this.lastRet = i];
}
}
}
场景2, ArrayList增强for循环中删除元素发生ConcurrentModificationException
public class Demo15 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
for (String str : list) {
list.remove(str);
}
}
}
分析:
集合中的增强for循环会翻译成迭代器(数组中增强for会翻译成普通for), 而在迭代器中又使用了ArrayList集合remove方法, 形结果就场景1一样, 抛异常了(注意: 这里说的是ArrayList, 但是写时复制容器CopyOnWriteArrayList却可以在增强for中使用集合的remove方法删除元素, 这是因为它的迭代器拷贝了原集合的元素, 后面进行详细分析)
场景3, 多线程迭代器发生ConcurrentModificationException
创建两个线程, 对同个集合分别获取迭代器, 进行循环删除元素
public class Demo15 {
static List<String> list = new ArrayList<>();
static {
list.add("a");
list.add("b");
list.add("c");
}
public static void main(String[] args) {
new Thread(() -> {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
iterator.next();
iterator.remove();
}
}).start();
new Thread(() -> {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
iterator.next();
iterator.remove();
}
}).start();
}
}
分析:
两个线程返回的是不同的迭代器, 每个迭代器都是独立的, 当线程1操作删除后, 更改了list的modCount, 同时线程1返回的迭代器中expectedModCount也更改了, 所以他们是相等的, 但是线程2的expectedModCount还是之前的, 没有做同步修改, 因为只有调用线程2中迭代器的remove才会同步, 所以线程2的迭代器成员变量expectedModCount和list的modCount不一致, 所以在调用线程2迭代器的next方法或者remove方法时, 会去检查修改次数是否一致, 不一致会抛异常ConcurrentModificationException