在Java给咱们提供的各类容器类中,最经常使用的就是ArrayList了吧,你可能早就把它用烂了,但它内部细节是怎么实现的,数组怎么动态增加的呢?今天咱们就来看一下ArrayList的源码一探个究竟。html
本文分析的ArrayList源码基于JDK 1.8java
首先打开ArrayList类,看一下这个类的定义:数组
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
复制代码
ArrayList继承自AbstractList,支持泛型。bash
而后实现了这些接口:dom
ArrayList还间接实现了Iterable和Collection接口。函数
ArrayList 中声明了下面这些属性:ui
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;
// ...
}
复制代码
ArrayList分别提供了下面三个构造函数,首先来看下咱们最常使用的无参构造函数:this
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
复制代码
很简单,调用了下父类的构造函数(点进去发现父类构造函数是个空实现),而后将以前声明的空对象数组EMPTY_ELEMENTDATA赋值给elementData属性。spa
接着看下能够传初始容量的构造函数:code
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数组设置对象。
先看下最经常使用的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的冲动。
好的,分状况讨论一下。
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自增。
接下来看下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中所有的元素都得日后移动一位。
咱们刚看完了添加单个元素,如今来看下如何添加多个元素,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就省去了后面的方法调用,有知道的朋友麻烦告诉我噢!
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。
访问数据比较简单,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对应的元素。
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。
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循环是从后往前的。
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
复制代码
contains方法是经过调用indexOf方法实现的,若是indexOf方法返回的数字大于等于零,说明对象o存在,不然返回-1。
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后面的元素都往前移一位,而后删除最后一个元素。
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,也不返回删除的元素。
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。
trimToSize()方法能够用来将对象数组的大小压缩到跟size同样大:
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = Arrays.copyOf(elementData, size);
}
}
复制代码
size()方法返回当前包含的元素大小
public int size() {
return size;
}
复制代码
判断当前ArrayList内的元素是否为空
public boolean isEmpty() {
return size == 0;
}
复制代码
返回包含当前ArrayList所有元素的对象数组
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
复制代码
清除当前ArrayList中所有元素
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
复制代码
就把对象数组中每一个元素设为null,而后size重置为0。
根据传进来的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)。