ArrayList是基于数组实现的,可以动态增长,它不是线程安全的,只能用于单线程,在多线程下要使用ArrayList结构,可以使用concurrent并发包下的CopyOnWriteArrayList类。
ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。
对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
private transient Object[] elementData; //存储ArrayList元素
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size; //元素数量
关键字transient: Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。
ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。
// ArrayList无参构造函数。默认容量是10。
public ArrayList() {
this(10);
}
// ArrayList带容量大小的构造函数。
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
// 新建一个数组
this.elementData = new Object[initialCapacity];
}
// 创建一个包含collection的ArrayList
public ArrayList(Collection extends E> c) {
elementData = c.toArray();
size = elementData.length;
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
ArrayList添加元素有多种方式,包括set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection< ? extends E> c)、addAll(int index, Collection< ? extends E> c),这里只对add的两种方法进行分析,并深入了解其如何进行扩容的问题,其它方法其原理大致相似,大家可以去看源码实现。
// 将指定的元素添加到此列表的尾部。
public boolean add(E e) {
ensureCapacity(size + 1);
elementData[size++] = e;
return true;
}
// 将指定的元素插入此列表中的指定位置。
// 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
// 如果数组长度不足,将进行扩容。
ensureCapacity(size+1);
// 将 elementData中从Index位置开始、长度为size-index的元素,
// 拷贝到从下标为index+1位置开始的新的elementData数组中。
// 即将当前位于该位置的元素以及所有后续元素右移一个位置。
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
从源码可以看到在数组容量不够的情况下,我们需要对数组进行扩容,代码中调用了ensureCapacity方法,接下来我们分析ensureCapacity方法源码的实现。
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// 在此把整个数组copy到一个新的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
从源码分析可以看到,ensureCapacity方法将整个数组Copy到新的数组中,这里发生第一次Copy,把原来的数组Copy到一个大约1.5倍大的新的数组里,新数组扩容的大小为:
int newCapacity = (oldCapacity * 3)/2 + 1;
第二次Copy发生在这里,index后边的数据都需要再次copy:
System.arraycopy(elementData, index, elementData, index + 1, size - index);
// 返回此列表中指定位置上的元素。
public E get(int index) {
RangeCheck(index);
return (E) elementData[index];
}
ArrayList提供两种删除元素方法:
删除list中指定位置的元素 remove(int index)
// 移除此列表中指定位置上的元素。
public E remove(int index) {
RangeCheck(index);
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // Let gc do its work
return oldValue;
}
通过元素对象找到此对象并删除 remove(Object o)
// 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。
public boolean remove(Object o) {
// 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 类似remove(int index),移除列表中指定位置上的元素。
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
}
注意:删除一个元素后,也可能导致该元素后面的元素发生左移。
fastRemove源码基本与remove(index)一致。
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
}
ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
深入Java集合学习系列:ArrayList的实现原理
Java集合—ArrayList的实现原理