java.util.ArrayList是最常用的集合类之一,阅读它的源码有助于帮助我们正确的使用它,并且学习它的思想。
在Java中,还有其他的一些集合类,它们的关系如下图
:
(图来源:Thinking in Java )
在源码中有一大段注释,介绍了ArrayList的相关信息,总结如下:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
RandomAccess, Cloneable, java.io.Serializable为标记接口
RandomAccess:表明其支持随机快速访问
Cloneable:标记其可以调用Object.clone()方法,并按字段复制。ArrayList重写了clone()方法。
Serializable:可以支持序列化。
// 默认的初始化容量为10
private static final int DEFAULT_CAPACITY = 10;
// 空数组,当创建一个容量为0的ArrayList时
private static final Object[] EMPTY_ELEMENTDATA = {};.
//默认的空实例数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//元素存储在此数组里,非私有是为了方便内部类访问,使用transient,该字段不会被序列化
transient Object[] elementData;
//元素的实际数量
private int size;
//可存储最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
ArrayList声明了两个空数组,DEFAULTCAPACITY_EMPTY_ELEMENTDATA用于使用无参构造器,创建未指明初始容量的集合,另一个用于创建初始容量为0的空数组,这会影响到首次向集合添加元素时的扩容。
3.1 创建一个默认大小的ArrayList
//1.无参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//2.指定初始化容量的构造器
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
}
//如果初始为0,则将数组初始化为 EMPTY_ELEMENTDATA
else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//3. 根据给定的Collection集合创建
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,会初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数组,而不是创建一个容量为DEFAULT_CAPACITY的数组,当向ArrayList里添加第一个元素时,会进行扩容,扩容后容量为DEFAULT_CAPACITY,即为10。如果当前容量无法满足需求,会自动扩容。
//添加铁元素的方法
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//如果为默认创建的空数组,添加一个元素,会初始化为DEFAULT_CAPACITY:10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//根据添加元素后所需的最小容量,判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果所需最小容量大于当前数组的容量,进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//右移一位,即乘以1/2,扩容到原容量1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果扩容后的容量小于所需的最小容量,则将数组扩容为所需的最小容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果扩容后大于MAX_ARRAY_SIZE,调用hugeCapacity()可以扩容到Integer.MAX_VALUE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//将原数组的内容复制到容量为扩容后大小:newCapacity的新数组中,其最终调用的是
//System.arraycopy方法,这是一个native方法
elementData = Arrays.copyOf(elementData, newCapacity);
}
3.2 通过Arrays.asList()方法创建
实际上,Arrays.asList(T…a)返回的并不是java.util.ArrayList,而是Arrays类中的一个静态内部类,它继承了AbstractList,所以它也是一个Collection,因此可以通过java.util.ArrayList的有参构造器public ArrayList(Collection extends E> c)创建。
通过Arrays.asList()创建的ArrayList是一个不可变长的数组,它没有重写基类AbstractList的add(Object o),remove(int index)等方法,调用这些方法会抛出UnsupportedOperationException。
ArrayList共有以下public方法
4.1 向集合中添加元素
共有4个添加元素的方法
//在集合最后添加一个元素
boolean add(E e)
//从一个元素添加到指定位置
void add(int index, E element)
//将另一个集合的元素添加到末尾
boolean addAll(Collection extends E> c)
//将另一个集合的元素从指定位置开始添加
boolean addAll(int index, Collection extends E> c)
boolean addAll(int index, Collection extends E> c)方法的源码如下:
public boolean addAll(int index, Collection<? extends E> c) {
//边界检查,index应在[0,size]范围
rangeCheckForAdd(index);
//将集合转为数组,最终通过System.arraycopy实现
Object[] a = c.toArray();
int numNew = a.length;
//确认所需容量
ensureCapacityInternal(size + numNew);
//确定需要移动元素的数量
int numMoved = size - index;
//如果需要移动元素,就把原数组从index开始,复制numMoved个元素,保存到目标数组elementData中,将原数组index后的元素一次保存在目标数组的index + numNew后。
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//将新添加的全部元素依次放入elementData中index到index+numNew的位置上
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
4.2 清空集合
public void clear() {
modCount++;
//引用指向null,交由gc负责回收
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
4.3 扩容
当容量不足时,会调用内部的private ensureCapacityInternal方法自动完成扩容。我们也可以进行手动扩容。
//手动扩容
public void ensureCapacity(int minCapacity) {
//如果当前数组是默认空数组,则minExpand 设为10,用户指定的容量minCapacity>10,会按用户指定的容量扩容
//如果当前数组不是默认空数组,即数组中有元素,或创建了容量为0的list,则用户指定的容量大于0才会执行后续语句。
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
//在ensureExplicitCapacity还会进行检查,minCapacity大于当前保存元素数组的长度才会扩容
ensureExplicitCapacity(minCapacity);
}
}
4.4 缩容
ArrayList的容量是大于等于其中保存元素的数量,可以把它的容量缩减到和保存的元素数量相同,以节省内存空间。尽量确保不再向其添加元素,避免缩容再扩容这种操作。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
4.5 从集合中获取元素
ArrayList是基于数组实现的,可以通过索引直接取得该位置的元素,时间复杂度O(1),ArrayList在随机访问有较好的性能,但是在中插入或移除元素,需要进行元素的移动,所以性能较差。LinkedList是由双链表实现的容器,在其中插入或移除元素性能较好,随机访问性能较差。
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
查找某个对象在集合中的位置
public int indexOf(Object o) {
//ArrayList允许加入null,并且允许重复的元素,此方法会从前向后遍历,返回第一个该元素的索引,lastIndexOf会从后向前遍历,返回该元素出现的最后位置,
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//线性查找,根据equals方法判断是否为同一个对象
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
4.6 迭代器 iterator
ArrayList 实现了List接口,List继承Collection接口,collection继承了Iterable接口,实现Iterable接口的类都要实现一个能够返回迭代器对象Iterator的iterator();方法。Iterator接口有4个方法
forEachRemaining 是jdk1.8中的新增方法,具有默认实现。
remove方法的默认实现会抛出UnsupportedOperationException(“remove”);。
hasNext():判断是否还有下一个元素。
next():返回下一个元素。
首先看ArrayList中对Iterable接口iterator()的实现,它返回了Itr对象,这是ArrayList的内部类,实现了Iterator接口。通过这个方法返回的迭代器是fail-fast快速失效的,并且它只能单向移动。ListIterator listIterator()返回一个双向移动的迭代器。
public Iterator<E> iterator() {
return new Itr();
}
Itr的代码如下:
private class Itr implements Iterator<E> {
int cursor; // 下一个待返回元素索引
// 返回的最后的元素的索引,初始化为-1,可以理解为指向第一个元素之前
int lastRet = -1;
int expectedModCount = modCount;//将预期修改计数初始化为modCount
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//在获取下个元素是,会进行modCount==expectedModCount的检查,为false会抛出ConcurrentModificationException(),
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
//cursor后移
cursor = i + 1;
//返回元素
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
//移除最新返回的元素,即最后调用next()之后的那个元素
ArrayList.this.remove(lastRet);
//索引lastRet位置的元素已被删除,后面的元素会依次前移,下一个待返回的索引是lastRet
cursor = lastRet;
//最新返回的元素已不存在,设为-1
lastRet = -1;
//删除会修改modCount,需要同步,避免迭代器失效
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
在ArrayList中,modCount属性用来实现fail-fast,当一个方法会对ArrayList的数组的容量进行改变,如trimToSize,ensureExplicitCapacity,或使存储结构发生改变,如add,remove,clear,modCount都会自增,替换操作set方法没有改变结构,所以modCount不会改变。
在for-each语句中,做出会修改modCount的操作,迭代器会失效,抛出ConcurrentModificationException()。
阅读完源码之后,应该知道在什么情况下使用ArrayList集合,在什么情况下使用其他集合,记忆如何正确的使用其中的方法。