基于jdk1.8,剖析源码,知其然知其所以然,才能更好的解决问题。
1、创建一个ArrayList对象
List<String> list = new ArrayList<>();
部分源码分析:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//默认初始化大小
private static final int DEFAULT_CAPACITY = 10;
//用于空实例的共享空数组实例。(指定集合大小的,初始化ArrayList集合)
private static final Object[] EMPTY_ELEMENTDATA = {};
//用于默认大小的空实例的共享空数组实例。我们将其与EMPTY_ELEMENTDATA区别开来,以便了解在添加第一个元素时应该膨胀多少。(默认的大小的,初始化ArrayList集合)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储数组列表元素的数组缓冲区。ArrayList的容量是这个数组缓冲区的长度。当添加第一个元素时,任何带有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList都会被扩展为DEFAULT_CAPACITY。
// transient关键字作用:被修饰的字段不可以被序列化,即只能存放在内存中,不可以传输
transient Object[] elementData;
//数组集合的实际数据的大小大小
private int size;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
}
分析:
初始化ArrayList 也就是将一个数组赋值给缓存变量
2、迭代器Iterator使用时,ArrayList直接删除为什么会报错?
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i+"a");
}
Iterator iterator = list.iterator();
while (iterator.hasNext()){
//执行代码,改行报错 java.util.ConcurrentModificationException
String ss = (String) iterator.next();
System.out.print(ss+"\t");
if (ss.equals("1a")){
list.remove(ss);
}
}
add源码分析:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
public boolean add(E e) {
//判断并更改集合大小
ensureCapacityInternal(size + 1); // Increments modCount!! -- 1
elementData[size++] = e;//给新增变量添加到数组结尾,并将大小减一
return true;
}
//-- 1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); //--2 --3
}
//--2 计算当前数组的最小空间大小
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果是默认大小的ArrayList则进行取默认大小值DEFAULT_CAPACITY=10与最小容量比较,取最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//--3 确保空间大小
private void ensureExplicitCapacity(int minCapacity) {
/*
结构修改是指改变列表的大小,或以其他方式干扰列表,从而导致正在进行的迭代可能产生不正确的结果。
该字段由迭代器和列表迭代器方法返回的迭代器和列表迭代器实现使用。如果该字段的值意外更改,迭代器(或列表迭代器)将抛出ConcurrentModificationException,以响应下一个、删除、上一个、设置或添加操作。这提供了快速失败行为,而不是不确定行为
*/
modCount++;//修改数据加1 此列表结构上被修改的次数 继承父类 AbstractList
// overflow-conscious code
if (minCapacity - elementData.length > 0)//如果最小空间值大于内存中默认的值则扩充数组
grow(minCapacity);//--4
}
//--4空间扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
增加元素流程图:

remove:
源码分析
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;//操作数加一
int numMoved = size - index - 1;//当前移除索引之后的元素个数
if (numMoved > 0)
//移除并交换
/*
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
src:源数组; srcPos:源数组要复制的起始位置; dest:目的数组; destPos:目的数组放置的起始位置;
length:复制的长度.
*/
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
}
list.iterator()的源码分析:
list.iterator()返回的Iterator为ArrayList的内部类Itr(),改类继承了Iterator<E>接口
private class Itr implements Iterator<E> {
// 游标,指向下一个元素
int cursor; // index of next element to return
//返回next获取的元素的值,默认为-1
int lastRet = -1; // index of last element returned; -1 if no such
//期望的修改数
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//重点 检查数组组在迭代时是否被修改过了
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];
}
final void checkForComodification() {
if (modCount != expectedModCount)//实际修改数与Iterator初始化时获得的数据不一致,说明遍历时被修改过抛出异常
throw new ConcurrentModificationException();
}
}
总结:
遍历数据时直接调用list.remove()报错,并不是因为list.remove()这行代码报错,而是因为,调用
list.remove()之后修改了modCount的值,再次调用iteratod的next()时,next()中会调用checkForComodification()方法比较修改数与期望的修改数时,与类中的expectedModCount初始赋值时的modCount不一致,抛出ConcurrentModificationException();
3、多线程下使用ArrayList报错原因
public static void main(String[] args) {
// List<String> list = new CopyOnWriteArrayList<>();
List<String> list = new ArrayList<>();
for (int i = 0; i < 200; i++) {
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("vvv"); //-------------1
// System.out.println(Thread.currentThread().getName()+"\t"+list);//-------------2
},String.valueOf(i)).start();
}
while (Thread.activeCount()>2){
}
System.out.println(list.size());
}
多线程情况下,执行list.add("vvv");时不会报ConcurrentModificationException();但是会报java.lang.ArrayIndexOutOfBoundsException异常,导致原因是:因为add方法是线程不安全的,当多个线程同时调用ensureCapacityInternal(size + 1);时,存在如下情况,例:缓存elementData[]的大小为10,当size为8时,有11个线程同时访问,size+1=9 ,9小于10 则不会扩充缓存,而elementData[size++] = e;中的size++会不断加一,则会超出缓存大小报异常;而且size++也不是线程安全的,所以如果执行的话,list集合元素的数会小于线程操作的次数
System.out.println(Thread.currentThread().getName()+"\t"+list);执行时会报ConcurrentModificationException();异常,因为调用toSting()时,会调用iteratod的next()方法遍历集合,当多线程执行时,每个线程会分别对操作数modCoun进行修改,当A线程刚刚进入执行toString(),赋值完modCount时,可能B线程刚刚执行完添加操作,操作数加一,A继续执行遍历时就会报错ConcurrentModificationException();异常