ArrayList源码分析
我们要了解ArrayList的源码,首先要了解它的继承和实现的接口。通过查看API我们可以了解到:
public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
我们可以来看看图解,比较直观(不得不承认,IDEA是最好的用的java编译器,哈哈):
从上图我们可以看出来ArrayList继承了AbstractList类,实现了三个接口:Serializable;Cloneable;RandomAccess;可以使ArrayList具有序列化和克隆的功能。
属性
接下来我们看一下ArrayList的属性都有哪些?
transient Object[] elementData; // non-private to simplify nested class access
private int size;
以上是他的两个属性,我们从中可以看出,ArrayList主要用elementData数组的形式来存储数据。见名知意。这一点也可以从属性名中看出来。而size就是ArrayList的长度了;
看完属性,其实里面还有一些常量值值得我们去观察。
1.默认属性容量
private static final int DEFAULT_CAPACITY = 10;
2.共享空常量数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//共享空常量数组(默认容量空数组)
private static final Object[] EMPTY_ELEMENTDATA = {};
//空数组
构造方法
1. ArrayList() 构造一个初始容量为 10 的空列表。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
从上面的构造方法中我们可以知道ArrayList就是先构造一个长度为10的空列表。但是它为什么是10?
其实我们通过查看源码,其实就可以发现,初始化的时候其实并没有给长度,是在add()方法中调用了ensureCapacityInternal()方法;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//里面调用了ensureCapacityInternal()方法,我们接着看ensureCapacityInternal()方法。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
其实可以从上面的代码看出来,就是说现在新建一个ArrayList的时候。肯定要使用add方法,如果你添加的长度小于10的话,就用10。超过十,就用你需要添加的长度。
2. ArrayList(Collection c)
构造一个包含指定 collection 的元素的列表
将容器Collection转化为数组赋给数组elementData,还对Collection转化是否转化为了Object[]进行了检查判断。如果Collection为空,则就将空的常量数组对象EMPTY_ELEMENTDATA赋给了elementData;
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;
}
}
3. ArrayList(int initialCapacity)
构造一个具有指定初始容量的空列表。
根据参数的大小作为容量来实例化底层的数组对象,其中对参数的3中情况进行了处理。当参数小于0时,抛异常。当参数等于0时,用空的常量数组对象EMPTY_ELEMENTDATA来初始化底层数组elementData。
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);
}
}
常用方法
add()
上面我们已经看过add的方法了,现在深入了解一下,ArrayList到底是怎么实现存储增加。
1.add(E e)
//首先add()方法进来,调用了ensureCapacityInternal()方法,把长度+1赋了进去,数组长度++,把元素添加到最后一个位置。并且返回的一直是true。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/*接下来就是ensureCapacityInternal()方法,其实我们从这个方法中可以看出elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
相等时,说明当前数组为空,则数组长度,则看现在的长度是否大于10,大于则为当前长度,小于则默认为10。
从这里我们可以明确看出来,我们使用无参构造的时候,默认长度为10。
*/
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
/*从这个方法又进入ensureExplicitCapacity()方法*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//我们再来看看grow()方法。
/*
这个grow()方法是一个很核心的方法。我们可以看到,它先是把旧数组的长度保存到oldCapacity里面。
然后又重新创建了一个newCapacity,它的长度是又旧数组的长度+旧数组长度向右移一位(相当于除以2)
---->这也就是我们常说的数组的扩容机制。
下面就是两个if判断,第一个判断是怕扩容的数组长度还是太小,就用minCapacity 来进行对数组的扩张。
第二个判断是如果扩张1.5倍太大或者是我们所需的空间大小minCapacity太大,则进行Integer.MAX_VALUE来进行扩张。
(其中hugeCapacity还有防止堆栈溢出,具体可以参考详细方法)
最后使用Arrays.copyOf()方法来实现对数组数据一个拷贝功能。
*/
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) //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
//最后再来看看Arrays.copyOf()方法的具体实现。最后归根结底是用的System.arraycopy()方法(这个方法命名不规范!因为很早就有这个方法了,所以一直没有改。)
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
//copyOf()方法的具体实现;
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;
}
2.add(int index, E element)
现在看这个方法就简单很多,就是插入到指定位置,其实就是对 System.arraycopy()方法的操作。System.arraycopy()改变的是第二个数组,就是把第一个数组从srcPos的位置开始复制length长度,到第二个数组的destPos位置。
像System.arraycopy(elementData, index, elementData, index + 1,size - index);
其实就是把第一个数组长度先扩容,然后在index位置,把后面的数组再往后移动一个位置。
例如:{1,2,3,4,5,} 变成{1,2,3,3,4,5}
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++;
}
get()
接下来我们继续看get()方法,顾名思义,get就是取值。我们具体看一下源码。
public E get(int index) {
rangeCheck(index);
//有效性检查
return elementData(index);
}
//我们在来看看elementData(index)方法。
E elementData(int index) {
return (E) elementData[index];
}
其实我们从这里就可以看出来,get()方法其实就是调用底层的数组elementData[],然后取指定下标的值。
set()
其实这个方法也很简单,因为底层使用的是数组,所以直接从数组中取出当前下标的元素,然后把新元素赋给当前位置,最终返回旧元素。
public E set(int index, E element) {
rangeCheck(index);
//有效性检查
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
remove()
1.remove(int index)
这个方法是删除指定位置的元素,首先取出index位置的元素,定义一个numMoved ,它的长度就是index元素之后的长度。然后还是调用arraycopy方法,把从index位置之后往前挪了一位,然后再把最后一位赋为null。让gc回收。
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;
}
2.remove(Object o)
这个方法是删除一个指定的元素,从源码中可以看到,无论是指定对象o是否为null,都是在ArrayList中找到与此第一个相等的元素的位置,然后调用fastRemove(index)来进行移除;如果没有找打指定对象o的位置,则返回false,表示没有移除成功。
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;
}
/*里面还有这个方法fastRemove(index);其实我们可以看出来这个和remove(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
}
remove方法待续…
clear()
最后我们来介绍一下这个方法,先来看一下源码。
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
其实这个方法也不难理解,就是循环底层数组,然后把所有的值都赋为null。
如有不正之处,希望大家可以指出,谢谢。