一:首先看下几个ArrayList循环过程删除元素的方法(一下内容均基于jdk7):
package list;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.prefs.Preferences;public class ListTest {public static void main(String[] args) {List list &#61; new ArrayList<>(Arrays.asList("a1", "ab2", "a3", "ab4", "a5", "ab6", "a7", "ab8", "a9"));// 迭代删除方式一for (String str : list) {System.out.println(str);if (str.contains("b")) {list.remove(str);}}// 迭代删除方式二int size &#61; list.size();for (int i &#61; 0; i ) {String str &#61; list.get(i);System.out.println(str);if (str.contains("b")) {list.remove(i);
// size--;
// i--;
}}// 迭代删除方式三for (int i &#61; 0; i ) {String str &#61; list.get(i);System.out.println(str);if (str.contains("b")) {list.remove(i);}}// 迭代删除方式四for (Iterator ite &#61; list.iterator(); ite.hasNext();) {String str &#61; ite.next();System.out.println(str);if (str.contains("b")) {ite.remove();}}// 迭代删除方式五for (Iterator ite &#61; list.iterator(); ite.hasNext();) {String str &#61; ite.next();if (str.contains("b")) {list.remove(str);}}}
}
方式一&#xff1a;报错 java.util.ConcurrentModificationException
方式二&#xff1a;报错&#xff1a;下标越界 java.lang.IndexOutOfBoundsException
list移除了元素但size大小未响应变化,所以导致数组下标不对&#xff1b;
list.remove(i)必须size--
而且取出的数据的索引也不准确&#xff0c;同时需要做i--操作
方式三&#xff1a;正常删除&#xff0c;不推荐&#xff1b;每次循环都需要计算list的大小&#xff0c;效率低
方式四&#xff1a;正常删除&#xff0c;推荐使用
方式五&#xff1a;报错&#xff1a; java.util.ConcurrentModificationException
二&#xff1a;如果上面的结果算错的话&#xff0c;先看下ArrayList的源码(add和remove方法)
ArrayList继承AbstractList,modCount是AbstractList中定义用于计算列表的修改次数的属性。
public class ArrayList extends AbstractList // AbstractList定义了&#xff1a;protected transient int modCount &#61; 0;
implements List, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID &#61; 8683452581122892189L;
//设置arrayList默认容量
private static final int DEFAULT_CAPACITY &#61; 10;
//空数组&#xff0c;当调用无参数构造函数的时候默认给个空数组&#xff0c;用于判断ArrayList数据是否为空时
private static final Object[]EMPTY_ELEMENTDATA &#61; {};
//这才是真正保存数据的数组
private transient Object[] elementData;
//arrayList的实际元素数量
private int size;
//构造方法传入默认的capacity 设置默认数组大小
public ArrayList(int initialCapacity) {
super();
if (initialCapacity <0)
throw new IllegalArgumentException("Illegal Capacity: "&#43;initialCapacity);
this.elementData &#61; new Object[initialCapacity];
}
//无参数构造方法默认为空数组
public ArrayList() {
super();
this.elementData &#61; EMPTY_ELEMENTDATA;
}
//构造方法传入一个Collection&#xff0c; 则将Collection里面的值copy到arrayList
public ArrayList(Collection extends E> c) {
elementData &#61; c.toArray();
size &#61; elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() !&#61; Object[].class)
elementData &#61; Arrays.copyOf(elementData, size, Object[].class);
}
//下面主要看看ArrayList 是如何将数组进行动态扩充实现add 和 remove
public boolean add(E e) {
ensureCapacityInternal(size &#43; 1); // Increments modCount!!
elementData[size&#43;&#43;] &#61; e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size &#43; 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index &#43; 1,size - index);
elementData[index] &#61; element;
size&#43;&#43;;
}
private void ensureCapacityInternal(int minCapacity) {
// 通过比较elementData和EMPTY_ELEMENTDATA的地址来判断ArrayList中是否为空
// 这种判空方式相比elementData.length更方便&#xff0c;无需进行数组内部属性length的值&#xff0c;只需要比较地址即可。
if (elementData &#61;&#61; EMPTY_ELEMENTDATA) {
minCapacity &#61; Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount&#43;&#43;;//ArrayList每次数据更新&#xff08;add,remove&#xff09;都会对modCount的值更新
//超出了数组可容纳的长度&#xff0c;需要进行动态扩展
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//这才是ArrayList动态扩展的点
private void grow(int minCapacity) {
int oldCapacity &#61; elementData.length;
//设置新数组的容量扩展为原来数组的1.5倍&#xff0c;oldCapacity >>1 向右位移&#xff0c;相当于oldCapacity/2, oldCapacity &#43; (oldCapacity >> 1)&#61;1.5*oldCapacity
int newCapacity &#61; oldCapacity &#43; (oldCapacity >> 1);
//再判断一下新数组的容量够不够&#xff0c;够了就直接使用这个长度创建新数组&#xff0c;
//不够就将数组长度设置为需要的长度
if (newCapacity - minCapacity <0)
newCapacity &#61; minCapacity;
//判断有没超过最大限制
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity &#61; hugeCapacity(minCapacity);
//将原来数组的值copy新数组中去&#xff0c; ArrayList的引用指向新数组
//这儿会新创建数组&#xff0c;如果数据量很大&#xff0c;重复的创建的数组&#xff0c;那么还是会影响效率&#xff0c;
//因此鼓励在合适的时候通过构造方法指定默认的capaticy大小
elementData &#61; Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity <0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
// 删除方法
public boolean remove(Object o) {
// Object可以为null
if (o &#61;&#61; null) {
// 如果传入的对象是null,则会循环数组查找是否有null的元素,存在则拿到索引index进行快速删除
for (int index &#61; 0; index
if (elementData[index] &#61;&#61; null) {
fastRemove(index);
return true;
}
} else {
// 对象非空则通过循环数组通过equals进行判断&#xff0c;最终还是要通过fastRemove根据索引删除
for (int index &#61; 0; index
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
// 快速删除方法&#xff1a;基于下标进行准确删除元素
private void fastRemove(int index) {
// 删除元素会更新ArrayList的modCount值
modCount&#43;&#43;;
// 数组是连续的存储数据结构&#xff0c;当删除其中一个元素&#xff0c;该元素后面的所有的元素需要向前移动一个位置
// numMoved 表示删除的下标到最后总共受影响的元素个数&#xff0c;即需要前移的元素个数
int numMoved &#61; size - index - 1;
if (numMoved > 0)
// 在同一个数组中进行复制&#xff0c;把(删除元素下标后面的)数组元素复制(拼接)到(删除元素下标前的)数组中
// 但是此时会出现最后那个数组元素还是以前元素而不是null
System.arraycopy(elementData, index&#43;1, elementData, index,numMoved);
// 经过elementData[--size] &#61; null则把数组删除的那个下标移动到最后&#xff0c;加速回收
elementData[--size] &#61; null; // clear to let GC do its work
}
}
三&#xff1a;看下ArrayList进行foreach时所调用的迭代器(内部迭代器Itr)
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet &#61; -1; // index of last element returned; -1 if no such
// expectedModCount是Itr特有的&#xff0c;modCount是公共的
// expectedModCount和modCount默认是两者相等的;ArrayList进行删除修改都会更新modCount的值
// 当ArrayList通过foreach进入它的内部迭代器Itr时&#xff0c;expectedModCount就被赋值为modCount的值&#xff0c;后续ArrayList进行增加或删除&#xff0c;只会更新modCount&#xff0c;而不会同步更新expectedModCount
// 所以迭代器根据这两个值进行判断是否有并发性修改
int expectedModCount &#61; modCount;
public boolean hasNext() {
return cursor !&#61; size;
}
// ArrayList通过foreach&#xff08;即增强for循环&#xff09;来循环是调用的是ArrayList中内部类Itr的next()
&#64;SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i &#61; cursor;
if (i >&#61; size)
throw new NoSuchElementException();
Object[] elementData &#61; ArrayList.this.elementData;
if (i >&#61; elementData.length)
throw new ConcurrentModificationException();
cursor &#61; i &#43; 1;
return (E) elementData[lastRet &#61; i];
}
// ArrayList中迭代器删除方法
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor &#61; lastRet;
lastRet &#61; -1;
// 通过ArrayList中foreach(即通过ArrayList内部Itr的迭代器)进行删除元素
// 此时会进行赋值 expectedModCount &#61; modCount;而不会抛出异常
expectedModCount &#61; modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount !&#61; expectedModCount)
throw new ConcurrentModificationException();
}
}
对此应该差不多可以理解了。ArrayList通过foreach迭代是调用的其内部类Itr的next方法。如果通过foreach循环,要去除某些元素&#xff0c;只能通过迭代器删除。因为迭代器删除后会对expectedModCount &#61; modCount设置&#xff0c;不会再循环过程因为expectedModCount 和 modCount值不相等而抛出异常了。如果是通过ArrayList的删除则只会对modCount进行更新&#xff0c;但是ArrayList内部迭代器Itr的属性expectedModCount却没有得到更新&#xff0c;所以抛异常。