ArrayList源码分析
1. ArrayList继承体系结构
class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
从源码可以看到ArrayList类主要是继承AbstractList类并实现了List接口,实现RandomAccess、Cloneable和Serializable接口,这使得ArrayList具有克隆和序列化的功能。
接下来将从构造函数开始介绍,之后介绍一些我们平常使用的关于ArrayList类的一些方法。
2.属性
ArrayList类中的几个主要属性,如下:
static final int DEFAULT_CAPACITY = 10;
static final Object[] EMPTY_ELEMENTDATA = {};
static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
int size;
2.1. transient Object[] elementData;
elementData数组用来存储ArrayList中的元素,从这个可以看出,ArrayList是底层是借组于数组来实现的。
2.2. private int size
此属性用来记录ArrayList中存储的元素的个数。
2.3.默认初始容量
private static final int DEFAULT_CAPACITY = 10;
2.4.两个是共享空常量数组
用于空的实例对象,第二个与第一个的区别文档上面是这么说的:We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added., 翻译一下就是与EMPTY_ELEMENTDATA数组的区别在于当第一个元素被加入进来的时候它知道如何扩张;在源码中函数add(E e)中的第一行代码中的所在的函数就是这句话的实现。
private static final Object[] EMPTY_ELEMENTDATA = {};
//下面这个是共享空常量数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
3.构造函数
ArrayList类共有3个构造函数,下面一一进行介绍。
3.1、无参构造函数
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
源码上介绍的功能为:构造一个初始容量为 10 的空列表。
即当我们不提供参数而new一个对象时,底层的数组就是直接用长度为10的空常量数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA进行实例化。可能有的人会问,此时DEFAULTCAPACITY_EMPTY_ELEMENTDATA的长度我们还不知道呀,从哪里可以看到是构造了一个初始容量为10的空列表呢?这里我们先不解释,在下面的add(E e)函数源码的介绍中会给出答案。
3.2、指定容量作为参数的构造函数
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);
}
}
从源码可以看到,就是根据参数的大小作为容量来实例化底层的数组对象,其中对参数的三种情况进行了处理。当参数小于0时,抛异常。当参数等于0时,用空的常量数组对象EMPTY_ELEMENTDATA来初始化底层数组elementData。
3.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;
}
}
从源码可以看到,将容器Collection转化为数组赋给数组elementData,还对Collection转化是否转化为了Object[]进行了检查判断。如果Collection为空,则就将空的常量数组对象EMPTY_ELEMENTDATA赋给了elementData;
4、常用方法
4.1 add方法
我们一般就是用ArrayList对象来存储数据,因此,首先要介绍的当仁不让就是add函数了。add有两种形式,下面将分别进行介绍。
4.1.1、add(E e)
函数功能:将制定的元素加入到List的末尾。
源码如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
此函数中使用到了ensureCapacityInternal函数,那我们就看一下这个函数,源码如下:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
从源码可以看出,如果elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则就用默认容量10来进行开辟空间。这里的源码就解释了DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组和EMPTY_ELEMENTDATA数组的区别之所在。也给出了当我们用无参构造函数来实例化一个对象时,确实是构造的一个长度为10的数组对象。上面代码中用到了ensureExplicitCapacity函数,我们看下此函数源码如下:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
看源码我一般喜欢跟着路线一直跟下去,既然上面的函数中提到了grow这个函数,下面就看下这个函数的源码,如下:
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
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);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
其中两个if的作用为处理两种情况:
1)第一种情况为:如果newCapacity扩展的过小。则应该至少扩张到所需的空间大小minCapacity
2)第二种情况为:newCapacity扩张的过大,如果过大,则用Integer.MAX_VALUE来代替。
从源码中可以看到,此函数的功能就是一个数组的扩张。一般情况下是扩展到原来数组长度的1.5倍。 但是,由于扩张1.5倍可能和我们的需要不一致,即可能太小,也可能太大。因此,就有了源码中的两个if条件的处理。即如果扩张1.5倍太小,则就用我们需要的空间大小minCapacity来进行扩张。如果扩张1.5倍太大或者是我们所需的空间大小minCapacity太大,则进行Integer.MAX_VALUE来进行扩张。
上面grow函数中最后一条语句elementData = Arrays.copyOf(elementData, newCapacity);的功能就是将原来的数组中的元素复制扩展到大小为newCapacity的新数组中,并返回这个新数组。Arrays.copyOf(elementData, newCapacity)的源码如下:
public static int[] copyOf(int[] original, int newLength) {
int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
此函数源码实现比较简单,功能上面也进行了描述。
而此时,我们又看到了另外一个类System中的arraycopy这个函数,下面我们也看下源码具体是怎么实现拷贝的。文档上此函数的说明如下:
static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。这个函数时一个原生态(即native)的方法,在System中的声明如下:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
到这里我们就把add(E e)函数及与此函数相关的函数的源码都看了下,这些我们就应该比较清晰这个函数的具体实现过程以及其中涉及到的一些细节。
4.1.2、add(int index,E element)
源码如下:
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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++;
}
此函数首先进行了位置的有效性检查,然后添加修改次数并判断是否需要扩张数组长度,最后完成数据从index开始的所有元素拷贝到index+1开始长度为size-index的位置上
看了add(E e)这个函数的实现过程之后,这个函数的实现过程也就相当容易理解了。上面讲解了增加元素,这个就介绍如何从ArrayList中获取元素。
***4.1.3、addAll(Collection
public boolean addAll(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;
}
***4.1.4、addAll(int index, Collection
public boolean addAll(int index, 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;
}
4.2 get(int index)
源码如下:
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
由于ArrayList底层是借助于数组来实现,因此,从ArrayList中获取元素就相当简单了。直接利用了数组随机访问能力强的特点。
4.3 set(int index,E element)
这个是改变ArrayList对象中某个位置元素的值的大小。源码如下:相信看到源码我们都能够看到他们它是如何实现的,相当简单哈。
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
4.4 remove:移除元素
移除ArrayList中的移除元素,包括两个函数的重载,一个是移除指定位置的元素,另一个是移除指定值的元素。下面分别进行介绍。
4.4.1 remove(int index):移除指定位置的元素
此函数的功能是:移除ArrayList对象中index位置的元素。
有了上面add(int index,E e)的实现思路,我们不难猜测其实现思路是:自身拷贝(即将数组从index+1位置开始到末尾的元素拷贝到从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;
}
4.4.2 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;
}
从源码中可以看到,无论是指定对象o是否为null,都是在ArrayList中找到与此第一个相等的元素的位置,然后调用fastRemove(index)来进行移除;如果没有找打指定对象o的位置,则返回false,表示没有移除成功。
方法中借助了fastRemove(int index)来实现,那我们也就看下这个函数的具体实现,源码如下:
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
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)会有很大的不同,看到源码之后,发现其实区别真心不大,唯一的区别是fastRemove函数没有对index进行有效性检查,以及没有返回移除的旧值,为什么不返回呢?这是因为remove(Object o)给出的就是要删除的值,因此不返回旧值是非常正常的。
4.5 clear
最后要介绍的函数就是这个了,清除ArrayList中所有的元素。直接将数组中的所有元素设置为null即可,这样便于垃圾回收。源码如下:
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
4.6 public List subList(int fromIndex, int toIndex) :截取子List
源码如下:
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
首先进行了位置有效性检查,然后构造了一个SubList对象并返回,我们看下SubList声明如下:
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
其继承了AbstractList,并实现了RandomAccess
到这里就将ArrayList源码进行了一个简单的分析和介绍。