ArrayList 是 Java 中很常用的集合之一。它基于数组实现了一个可变长度的集合。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
此实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须 保持外部同步。(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法将该列表“包装”起来。这最好在创建时完成,以防止意外对列表进行不同步的访问:
List list = Collections.synchronizedList(new ArrayList(...));
四个私有属性
// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 当调用无参构造函数时使用的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 真正的保存数据的数组 transient关键字说明其不能被序列化
// 不能序列化的原因是,数组内存储的的元素其实只是一个引用,单单序列化一个引用没有任何意义,反序列化后这些引用都无法在指向原来的对象。
transient Object[] elementData;
// 数组的实际元素数量
private int size;
构造函数
// 给与一个初始大小来构造集合,新建给定大小的数组
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);
}
}
// 无参的构造函数,新建的数组为空数组,在第一次添加元素时大小扩展为10
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 构造方法传入一个Collection, 则将Collection里面的值copy到ArrayList
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;
}
}
成员函数
容量控制
trimToSize
将 ArrayList 的容量设置为当前 size 的大小。由于是由数组来实现的,ArrayList 申请的空间大小是要比实际元素占用的空间要大一些,此函数去掉 null 值的空间。
modCount 是从 AbstractList 中继承的变量,当集合的结构发生改变时,它会自增在原值上加一,这样可以保证在多线程的环境下,如果一个线程对 List 进行了结构上的改变时,modCount 自增,另一个线程在迭代的时候,由于每一次例如 next、remove、previous、set 或 add 操作方法都会检查原 expectedModCount 值和当前 modCount 值是否相等,此时显然两者不等,就会报错 ConcurrentModificationException 。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
ensureCapacity
ArrayList 在自动扩容时,每次数组容量的增长大约是其原容量的1.5倍。会重新建立这个约1.5倍大小的新数组,然后将老数组的数据拷贝到新数组中去,所以,数组的频繁扩张会造成很大的开销。因此,在初始化时,尽量估算容量需求,尽量避免数组扩容;或者通过调用 ensureCapacity 方法来手动增加容量。
public void ensureCapacity(int minCapacity) {
// 判断当前数组是否为空数组,如果不为空,最小增长空间为0;如果为空,那么最少需要10的空间
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
// 如果所需空间大于最小增长空间,那么按所需空间来增长
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private void ensureExplicitCapacity(int minCapacity) {
// 快速失败变量 modCount 值在List结构变化时都要自增加一
modCount++;
// 所需空间大于elementData数组当前的空间时,才需要增长
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 数组的最大容量定义为最大整数少一个字节,(虚拟机会存入一些数组的信息,减少一个字节,更大程度的避免内存溢出)
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// 获取当前elementData数组的长度
int oldCapacity = elementData.length;
// 新长度扩容至1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 判断新长度和所需长度的大小,如果1.5倍还小,那就使用所需的长度;如果1.5倍可以了,那就使用1.5倍的大小
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 判断新长度有没有超过MAX_ARRAY_SIZE,如果超过了,进入hugeCapacity函数处理,那么看所需长度是否超过了MAX_ARRAY_SIZE,如果所需长度仍超出,那么新长度就设为Integer.MAX_VALUE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间,并将elementData的数据复制到新的内存空间
elementData = 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;
}
元素添加
set(int index, E element)
在指定位置添加新元素,返回老元素
public E set(int index, E element) {
// 越界就抛越界异常
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
add(E e)
在 ArrayList 尾部添加元素时,会调用 ensureCapacityInternal 这个方法来扩张数组。(ensureCapacity 是 public 的,可以手动调用,这个 ensureCapacityInternal 是添加时调用的 private 函数)
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 判断当前数组是否为空,如果为空则返回默认容量10和需求容量minCapacity之间的较大者
// 不为空,就返回所需容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
// 快速失败变量 modCount 值在List结构变化时都要自增加一
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
add(int index, E element)
调用了 System.arraycopy 方法来实现数组的复制。其实就是将数组指定位置之后的后移一位,然后将新元素插入到指定位置上。
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
addAll(Collection<? extends E> c)
将该collection中的所有元素添加到此列表的尾部。
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
addAll(int index, Collection<? extends E> c)
从指定的位置开始,将指定collection中的所有元素插入到此列表中。
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
元素删除
remove(int index)
检查范围,modCount 自增,然后保存指定位置的旧元素,执行数组拷贝,然后返回旧元素。(最后位置的元素由于已经没有意义,置为 null)
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
remove(Object o)
通过遍历数组来寻找待删除的对象,一旦找到第一个,就执行快速删除(类似 remove,但不判断越界,不返回旧元素)
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)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
removeRange(int fromIndex, int toIndex)
给定起始和终止位置,其中的元素全部被移除。(和 String 的 substring 的起始和终止类似,toIndex 位置的那个元素不删除)
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
clear()
数组中元素全部置为 null ,等 GC 回收
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
元素读取
get(int index)
返回指定位置上的元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
转为静态数组
toArray()
调用 Arrays.copyOf 方法,返回一个新数组,新数组拷贝了 elementData 从 0 至 size-1 位置的元素共 size 个并返回。
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
toArray(T[] a)
传入数组的长度小于 size,将返回一个 size 长度的数组。如果传入数组长度大于 size,那么后边多出的位置都置为 null 。
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}