研究ArrayList源码有一段时间了,说实话收益还是很大的。主要当方法的实现比较清楚时,用起来也比较放心。并且集合相关的源码对理解数据结构还是有点帮助的。话不多说,开始分析。
(1)ArrayList实现了List接口,List接口中定义了list中通用的很多方法。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
...<span style="font-family: Arial, Helvetica, sans-serif;">}</span>
(2)ArrayList里的属性。其中elementData就是底层用来存储ArrayList中元素的具体数组。
private static final long serialVersionUID = 8683452581122892189L;
//默认初始化容量
private static final int DEFAULT_CAPACITY = 10;
//当不指定ArrayList大小时,elementdata<span style="font-family: Arial, Helvetica, sans-serif;">默认</span>初始化<span style="font-family: Arial, Helvetica, sans-serif;">是一个空的数组</span>
private static final Object[] EMPTY_ELEMENTDATA = {};
//存储ArrayList里元素的数组<span style="white-space:pre"> </span>
private transient Object[] elementData;
//包含元素的数量
private int size;
//数组最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
(3)构造方法
相比于之前的ArrayList的实现,之前当不指定ArrayList初始化容量时,默认在调用构造方法时,初始化elementData容量为DEFAULT_CAPACITY(即10)。现在只会在调用add方法第一次添加元素时,会初始化容量。起到一点节约内存的作用。
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;//当不传入大小时,默认是一个空的数组
}
//根据其他集合构造List
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
if (elementData.getClass() != Object[].class)//toArray返回的可能不是Object[]类型
elementData = Arrays.copyOf(elementData, size, Object[].class);//[1]调用该方法返回Object[]数组
}
以上ArrayList(Collection<? extends E> c)这个构造方法根据传入的集合创建ArrayList。当传入的集合调用toArray方法返回的数组类型不是Object[]数组时,在[1]处调用Arrays工具类中的copyOf方法返回一个Object[]类型的数组并赋给elementData,由此达到创建ArrayList的目的。
Arrays类中的copyOf方法
[1].Arrays类中的copyOf方法
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
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;
}
(4)boolean add(E e)方法:该方法用于向list末尾添加一个指定类型的元素。每次添加元素时,需要调用ensuerCapacityInternal方法来确保elementData数组的内部容量足够存储新添加的元素e。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!![2]
elementData[size++] = e;//添加元素e,随后size+1
return true;
}
ensureCapacityInternal方法如下,该方法只会对elementData数组容量为0时(即无参构造方法中this.elementData = EMPTY_ELEMENTDATA;),指定elementData的最小容量为DEFAULT_CAPACITY或add中传入的大小中的较大者。如果elementData数组大小不为0,则会继续调用ensureExplicitCapacity方法(确保数组的明确容量)。
[2]处调用ensureCapacityInternal(size+1)保证每次添加元素时数组容量够用
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {//如果elementData是空数组
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//最小容量是size+1或默认容量(10)中的最大值
}
ensureExplicitCapacity(minCapacity);//[3]
}
ensureExplicitCapacity方法中,当发现要存储元素的容量(size+1)大于elementData的长度时,则调用grow方法对数组扩容。
[3]处调用ensureExplicitCapacity(minCapacity)方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)//如果指定的容量超出了elementData数组的现有容量,要扩容
grow(minCapacity);//[4]
}
千呼万唤始出来,grow方法设置数组新容量为旧的数组容量+旧的数组容量右移一位。和size+1(这是能容纳add进来的元素的最小容量minCapacity)比较,如果小于,则使用minCapacity。如果newCapacity超出了MAX_ARRAY_SIZE,还需要调用hugeCapacity方法进一步处理。
[4]处调用了grow(minCapcity)实现扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//新容量为旧的数组容量+旧的数组容量右移一位
if (newCapacity - minCapacity < 0)//和minCapacity比较,至少也要能容纳新添加进来的元素
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);//[5]
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//数组扩容
}
当容量超出Integer.MAX_VALUE时(即minCapacity<0),内存溢出。
[5]处调用hugeCapacity(minCapacity)
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow.容量超出Integer最大值(即为负数),内存溢出
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?//最大容量为Integer.MAX_VALUE或 MAX_ARRAY_SIZE
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
通过以上几个繁复的方法后,终于可以保证add方法容量够用了,此时add方法在elementData下标size+1处出入元素e即可。
(5)addAll方法
boolean addAll(Collection<? extends E> c):将集合c中的所有元素添加到elementData的末尾。
将要添加的集合调用toArray()转换为数组,并得到数组长度numNew.然后确保elementData的容量(依然调用ensureCapacityInternal方法)足够添加新的集合.。
将要添加的集合中的所有元素复制到elementData从size位置往后的位置,修改size.
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;//如果要添加的元素中没有元素,返回false
}
boolean addAll(int index,Collection<? extends E>)重载的方法。从elementData指定下标位置开始添加集合c中的元素。判断需要移动元素的数量。再调用arraycopy具体复制。
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);//下标检查
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); //确保容量
//从index及其以后的所有元素都需要移动,以便插入新的集合元素
int numMoved = size - index;
//如果需要移动,将index及其以后的所有元素移动到从index+numNew及其以后的位置处
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//将要添加的集合复制进来
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
看完所有集合关于增add,addAll的操作。总结起来,涉及向ArrayList中添加元素之前,要先调用ensureCapacityInternal方法确保内部容量。然后可能会出现接下来的方法调用。
ensureCapacityInternal->ensureExplicitCapacity->grow->hugeCapacity,均是为了保证分配合适的容量用来添加元素。如果涉及根据指定下标添加元素时,需要调用rangeCheckForAdd方法来保证下标的正确性。紧接着,就是向elementData数组中添加元素的相关操作了。如果添加一个元素,直接在相应位置添加。如果添加多个元素,使用System.arraycopy方法或Arrays.copyof方法等进行操作。
(6)看完增(add),接下来再看删(remove)的相关方法。
E remove(int index):list中独有的根据指定下标来移除元素的方法。
先调用rangeCheck方法确保要移除元素的下标的正确性。由于要删除该index下标处的元素,所以index往后的所有元素响应的要向前移动一位。使用System.arraycopy方法,将elementData从index+1到最后的所有元素复制到index开始往后。最后将之前elementData中size-1位置的元素(list中的最后一个元素)置为null。返回移除的值。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
//需要移动的元素的数量
int numMoved = size - index - 1;
if (numMoved > 0)//如果数量大于0,移动
//将从index+1以及之后的所有元素向前移动
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;//返回值为移除的值
rangeCheck方法如下
调用rangeCheck方法,检查下标,若下标越界,抛异常
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
boolean remove(Object o):该方法在集合中移除指定的元素。在遍历集合过程中,如果找到指定元素,则调用fastRemove方法移除该元素。
remove(Object o):该方法根据指定的元素在集合中移除该元素
public boolean remove(Object o) {
//如果要移除的元素为null,遍历集合找到为null的元素移除
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);//[8]
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
调用fastRemove来移除元素,该方法和remove(int index)方法相比,就少调用了rangeCheck(index)而已,
因为上面的remove(Object o)就是遍历下标,根据下标进行移除操作,所以不会出现下标越界情况,无需判断;
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
}
clear
将集合中所有元素的引用设置为null,让垃圾回收器去解决吧.别忘了把size设置为0,代表现在list中没有元素
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
(7)看完了增(add,addAll)删(remove,clear)操作,再来看看改(set)相关的方法。
set(int index,E element):该方法用于在指定下标处替换原来的元素,返回原来的元素。涉及下标相关的都要进行rangeCheck操作。
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
(8)增删改都介绍完了,下面介绍查(get)相关的方法。
get(int index)
get:该方法根据指定下标返回集合中的元素
public E get(int index) {
rangeCheck(index);//[7]
return elementData(index);
}
indexOf():该方法返回集合中要查询元素第一次出现的下标。
public int indexOf(Object o) {
if (o == null) {//如果要查询的元素为null
for (int i = 0; i < size; i++)
//直接找到elementData中元素为null的下标并返回即可
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
//遍历集合,如果equals,则返回下标
if (o.equals(elementData[i]))
return i;
}
return -1;//查无,则返回-1
}
boolean contains(Object o):
public boolean contains(Object o) {
//由于indexOf方法在查询不到指定元素时返回-1,查询到时返回下标,所以通过>=0即可判断是否包含指定元素o
return indexOf(o) >= 0;
}
int 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;
}
(9)toArray方法:该方法将elementData从下标0开始size长度的元素(即所有存储在集合中的元素)复制并返回。
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
T[] toArray(T[] a)重载的方法,需要指定一个类型的数组,并返回该类型的数组。如果指定的数组大小小于size,则返回一个包含ArrayList中所有元素的新数组,否则复制并将size下标处的元素设置为null
public <T> T[] toArray(T[] a)
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;//a数组size下标处置为null
return a;
}
举个例子。
@Test
public void testToArray(){
List<Integer> list=new ArrayList<Integer>();
for(int i=0;i<5;i++){
list.add(i);
}
Integer[] arr=list.toArray(new Integer[]{11,22,33,44,55,66,77,88});
System.out.println(Arrays.toString(arr));//[0, 1, 2, 3, 4, null, 77, 88]
}
就说到这里,写了好几个小时,准备了好久,有不足之处和错误之处还望指正。