在Java给我们提供的各种容器类中,最常用的就是ArrayList了吧,你可能早就把它用烂了,但它内部细节是怎么实现的,数组怎么动态增长的呢?今天我们就来看一下ArrayList的源码一探个究竟。
本文分析的ArrayList源码基于JDK 1.8
1. ArrayList 的定义
首先打开ArrayList类,看一下这个类的定义:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
复制代码
ArrayList继承自AbstractList,支持泛型。
然后实现了这些接口:
- List 说明该类是一个有序的序列集合。
- RandomAccess 说明该类支持时间复杂度为O(1)随机访问;RandomAccess接口一般只用在实现了List接口的类。
- Cloneable 说明该类支持被克隆。
- Serializable 说明该类支持序列化。
ArrayList还间接实现了Iterable和Collection接口。
2. ArrayList 的属性
ArrayList 中声明了下面这些属性:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// 序列化ID
private static final long serialVersionUID = 8683452581122892189L;
// ArrayList的默认初始容量
private static final int DEFAULT_CAPACITY = 10;
// 一个空的对象数组,用来初始化内容为空的ArrayList实例
private static final Object[] EMPTY_ELEMENTDATA = {};
// 数据对象数组,真正的数据都保存在这个数组之中,标了transient关键字说明该对象数组不参与序列化
transient Object[] elementData;
// 当前ArrayList中包含的元素的个数
private int size;
// 当前ArrayList被修改过的次数
protected transient int modCount = 0;
// ...
}
复制代码
3. ArrayList 的构造函数
ArrayList分别提供了下面三个构造函数,首先来看下我们最常使用的无参构造函数:
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
复制代码
很简单,调用了下父类的构造函数(点进去发现父类构造函数是个空实现),然后将之前声明的空对象数组EMPTY_ELEMENTDATA赋值给elementData属性。
接着看下可以传初始容量的构造函数:
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
复制代码
也是先调用父类构造函数,然后判断如果传进来的初始化容量initialCapacity小于0则抛出异常,否则创建一个大小为initialCapacity的对象数组并赋值给elementData。
最后还有个可以传集合的构造函数:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
复制代码
该方法要求传入实现了Collection接口的类的对象,然后调用Collection类中的toArray()方法即可将该集合对象中包含的全部元素转换为对象数组。 接着再初始化size。
按道理来说这就应该初始化完毕了吧,可是为什么后面又紧跟了一句:
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
复制代码
原来Collection类的toArray()返回的不一定是Object[]类,举个例子:
public static void main(String[] args) {
Collection c = Arrays.asList("a", "b", "c");
System.out.println(c.toArray().getClass());
List<Object> list = new ArrayList<>(c);
list.set(0,new Object());
}
复制代码
运行会发现,打印输出的是class [Ljava.lang.String;
,因此此时ArrayList内部的elementData即为String类型了,这时候调用最后一句list.set(0,new Object())则会抛出ArrayStoreException异常,因为你不能往一个String数组设置对象。
4. ArrayList 添加数据
4.1 add(E e)
先看下最常用的add(E e)方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
复制代码
该方法先调用了ensureCapacityInternal()方法,从方法名可以猜测,这个方法应该就是用来对数组进行动态扩容的,跳转到该方法:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
复制代码
该方法的参数minCapacity表示当前要求的最小容量,我们前面传过来的是size + 1,即最小容量只要比当前元素数量多1就够了。
如果elementData == EMPTY_ELEMENTDATA,则说明该ArrayList是通过无参构造函数构造的,这时候我们的minCapacity取默认容量(10)的和传进来最小容量中较大的那一个。
接着又将minCapacity传给ensureExplicitCapacity()方法:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
复制代码
先自增了一下modCount,然后接着的代码就好玩了,为什么不直接
if (minCapacity > elementData.length){
grow(minCapacity);
}
复制代码
而要做个减法跟0比较呢?然后他这儿还有个注释,说overflow-conscious code又是怎么回事呢?
好接着点进grow()方法:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
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);
elementData = Arrays.copyOf(elementData, newCapacity);
}
复制代码
看到这里,我不禁产生了关掉IDE的冲动。
好的,分情况讨论一下。
- 先假设传进来的minCapacity = 11,对象数组elementData目前大小是10,即oldCapacity = 10,newCapacity在oldCapacity的基础上增加一半,即newCapacity = 15;newCapacity - minCapacity = 4 > 0,第一个条件不通过;newCapacity - MAX_ARRAY_SIZE显然小于0,第二个条件也不通过,所以到下一步newCapacity还是等于15,然后调用Arrays.copyOf(elementData, newCapacity)创建了一个长度为15的数组副本,将原数组中的数据拷贝到这个数组副本中,最后将数组副本赋值给elementData。看一下Arrays.copyOf()方法的源码就知道了:
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
复制代码
-
现在我们假设minCapacity = 16,对象数组elementData的大小还是10,那么跟刚刚唯一的区别就是newCapacity - minCapacity = -1 < 0,第一个条件通过,即newCapacity = 16,剩下还是一样。
-
接下来就好玩了,如果当前elementData的大小已经超级大了,我是说超级超级大,大到了接近Integer.MAX_VALUE。
我们现在假设elementData的大小为Integer.MAX_VALUE - 100(即2147483547),则minCapacity = Integer.MAX_VALUE - 99(即2147483548),那么此时再计算newCapacity则会溢出,此时newCapacity = -1073741976,变成了负数;此时newCapacity - minCapacity = 1073741772 > 0,不满足第一个条件;newCapacity - MAX_ARRAY_SIZE = 1073741681 > 0, 通过第二个条件,调用hugeCapacity方法:
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
复制代码
hugeCapacity方法先判断minCapacity是不是溢出了,如果溢出了就抛出OutOfMemoryError,接着如果minCapacity没有溢出但是比MAX_ARRAY_SIZE大,则返回Integer.MAX_VALUE,否则就返回MAX_ARRAY_SIZE。 在我们举例的这个情况下,hugeCapacity方法返回MAX_ARRAY_SIZE,因此最终的newCapacity就等于MAX_ARRAY_SIZE。
接下来考虑一下,如果我们把grow方法改写成下面这样会发生什么:
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity < minCapacity)
newCapacity = minCapacity;
if (newCapacity > MAX_ARRAY_SIZE)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
复制代码
还是套用我们刚才的数据,oldCapacity = Integer.MAX_VALUE - 100(2147483547),minCapacity = Integer.MAX_VALUE - 99(即2147483548),newCapacity = -1073741976。会发现,修改过得代码跟原代码的运作刚好相反,第一个条件通过,第二个条件则不通过,即不会调用hugeCapacity方法。这样的话ArrayList就无法正确地扩容。
到这里扩容部分的代码就结束了,回来看一下我们开始的add方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
复制代码
正确扩容后,直接将元素e赋值给对象数组elementData下标为size的位置,因为此时size就是添加的元素应该在的下标,然后将size自增。
4.2 add(int index, E element)
接下来看下add(int index, E element)方法,在特定的下标index处插入元素:
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));
}
复制代码
首先调用了rangeCheckForAdd方法检查下标index是否合法,不合法抛出异常IndexOutOfBoundsException。
然后是调用ensureCapacityInternal方法进行扩容,确保ArrayList容量够存放size + 1个元素。
接下来的System.arraycopy(elementData, index, elementData, index + 1,size - index)
做的是将从index开始的元素全部都往后移动一位,如果原来的元素是[1,2,4,5],index = 2,element = 3,经过这步会变成[1,2,4,4,5]。
接着就把元素element覆盖到数组中index的位置。即变成了[1,2,3,4,5]。
最后将size自增。
该方法最坏的情况是index = 0,因为这样的话当前ArrayList中全部的元素都得往后移动一位。
4.3 addAll(Collection<? extends E> c)
我们刚看完了添加单个元素,现在来看下如何添加多个元素,ArrayList提供了addAll(Collection<? extends E> c)方法:
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;
}
复制代码
该方法的参数也是Collection接口,调用Collection提供的toArray方法将Collection中的全部元素转换为对象数组a;接着获取该数组的大小并赋值给numNew,然后同样是调用ensureCapacityInternal进行扩容。确保ArrayList容量可以存放size + numNew个元素后,直接将对象数组a中的全部元素从末尾拷贝进elementData。最后正确增加size,如果numNew不为0返回true,否则返回false。
这里我不理解的是,为什么不先判断集合是否为空,如果为空直接返回false就省去了后面的方法调用,有知道的朋友麻烦告诉我噢!
4.4 addAll(int index, Collection<? extends E> c)
ArrayList还提供了addAll(int index, Collection<? extends E> c)方法,可以从指定位置index开始插入多个元素:
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);
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;
}
复制代码
同样先通过调用rangeCheckForAdd方法判断index是否合法,index合法后接下来还是先调Collection的toArray方法转换为对象数组a,然后一样调用ensureCapacityInternal方法。
跟前面的addAll方法不同的是,我们这里可能要移动一些元素,因此通过size - index先计算要移动的元素个数,如果numMoved大于0则说明需要移动元素,即将index开始的元素统统往后numNew位;否则numMoved等于0,说明直接从末尾添加,无需移动元素。
接下来将对象数组a中的全部元素从index开始拷贝到elementData中。最后正确增加size。如果numNew不等于0返回true。
5. ArrayList 访问数据
访问数据比较简单,ArrayList提供了get()方法:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
return (E) elementData[index];
}
复制代码
首先调用rangeCheck(int index)方法检查下标是否超出了范围,然后通过调用封装好的elementData(int index)方法获取index对应的元素。
6. ArrayList 查找数据
6.1 indexOf(Object o)
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
复制代码
indexOf分两种情况,如果查找的对象为null,则通过for循环找到第一个为null的元素的位置并返回了;如果查找的对象不为null,则通过调用equals()方法判断是否同一个元素,找到即返回位置;如果没找到返回-1。
6.2 lastIndexOf(Object o)
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
复制代码
lastIndexOf()方法跟indexOf()方法唯一的区别就是它的for循环是从后往前的。
6.3 contains(Object o)
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
复制代码
contains方法是通过调用indexOf方法实现的,如果indexOf方法返回的数字大于等于零,说明对象o存在,否则返回-1。
7. ArrayList 删除数据
7.1 remove(int index)
remove(int index)方法用来删除指定下标的元素:
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方法的操作是,把index后面的元素都往前移一位,然后删除最后一个元素。
7.2 remove(Object o)
remove(Object o) 可以用来删除某指定元素
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;
}
复制代码
该方法跟indexOf(Object o)非常相似。remove(Object o)方法找到元素所在位置后,调用fastRemove(int 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; // clear to let GC do its work
}
复制代码
fastRemove(int index)跟remove(int index)方法的区别是,它不检查index,也不返回删除的元素。
8. ArrayList 修改数据
ArrayList提供了set(int index, E element)方法来更新某位置的值,也比较简单:
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
复制代码
先调用rangeCheck()方法保证index合法,接下来调用elementData()方法获取index位置的元素并保存在局部变量oldValue中,然后将该位置的元素更新为element,最后返回oldValue。
9. 其他
9.1 trimToSize()
trimToSize()方法可以用来将对象数组的大小压缩到跟size一样大:
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = Arrays.copyOf(elementData, size);
}
}
复制代码
9.2 size()
size()方法返回当前包含的元素大小
public int size() {
return size;
}
复制代码
9.3 isEmpty()
判断当前ArrayList内的元素是否为空
public boolean isEmpty() {
return size == 0;
}
复制代码
9.4 toArray()
返回包含当前ArrayList全部元素的对象数组
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
复制代码
9.5 clear()
清除当前ArrayList中全部元素
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
复制代码
就把对象数组中每个元素设为null,然后size重置为0。
9.6 sort(Comparator<? super E> c)
根据传进来的Comparator对ArrayList中的元素进行排序:
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
复制代码
具体的排序通过调用Arrays中的静态方法sort(T[] a, int fromIndex, int toIndex,Comparator<? super T> c)。