1.概述
ArrayList是一个可以实现动态调整大小的一个数组的实现的列表。
这个列表可以允许存储包括null在内的所有元素。
ArrayList和java.util.Vector功能大致相似,只是ArrayList是不同步的。
2.ArrayList的层次关系
如图所示,ArrayList继承了AbstractList类。实现了List, RandomAccess, Cloneable, java.io.Serializable接口。
AbstractList:
AbstractList的层次如图,也实现了List接口,ArrayList也实现了List接口,查阅资料后比较靠谱的一个答案就是,作者在写这段代码时候,感觉在ArrayList时间List会有一些帮助,但实际上没有用,也没有影响,所以就同时存在了。继承AbstractList,让AbstractList实现List接口,是因为AbstractList是一个抽象类,抽象类可以有抽象方法和具体实现,AbstractList本身实现List接口的方法,这样ArrayList继承AbstractList后就可以直接使用这些通用的方法了,而不必然每个List的实现类都去重复的编写这些通用方法的代码,代码看起来更简洁。
RandomAccess
ArrayList实现了RandomAccess接口,点击去看,RandomAccess里什么都没有,如图:
是一个空的接口,什么都没有,为什么ArrayList要实现这个接口呢。其实可以和LinkedList对比一下。LinkedList没有实现RandomAccess接口,如图:
通过查看RandomAccess的API,可以知道,RandomAccess是一个标记接口(Marker interface),List实现使用的标记接口,表示它们支持快速(通常是恒定时间)随机访问,此接口的主要用途是允许通用算法在应用于随机序列访问列表时改变其行为以提供良好性能。通俗点说,实现了RandomAccess接口,使用普通的for循环查询元素,性能更好。查看java.util.Collections类中源码:
如果list实现了RandomAccess,那么就可以用indexedBinarySearch(list, key)这个方法,indexedBinarySearch(list, key)这个方法的实现如图:
而LinkedList使用迭代器遍历,如图:
综上所述,实现RandomAccess接口,可以ArrayList实现更快速的查找元素。
Cloneable
实现Cloneable接口,可以返回此ArrayList实例的浅表副本。 (元素本身不会被复制。),是一个克隆方法。
java.io.Serializable
实现java.io.Serializable接口,ArrayList可以被序列化。
3.ArrayList的属性
序列化的版本号。
private static final long serialVersionUID = 8683452581122892189L;
默认的初始化容量
private static final int DEFAULT_CAPACITY = 10;
一个空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
用于默认大小的空实例的共享空数组实例。 将此与EMPTY_ELEMENTDATA区分开,以了解添加第一个元素时要扩容多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
存储ArrayList元素的数组缓冲区.ArrayList的容量是此数组缓冲区的长度。非私有,以简化嵌套类访问。
transient Object[] elementData;
实际数组的长度
private int size;
要分配的数组的最大大小。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
4.ArrayList的构造方法
无参构造方法:
无参构造方法会构造一个初始容量为10的空的列表。
带有initialCapacity参数的构造方法
如果initialCapacity大于等于0,会初始化一个initialCapacity大小的elementData数组,如果initialCapacity小于0,抛出异常。
带有参数Collection<? extends E> c的构造方法
这个构造方法会构造一个包含指定集合元素的列表。
5.常用的方法
add(E e)方法
源码为:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
该方法是在列表尾部添加一个元素。
第一行:ensureCapacityInternal(size + 1); // Increments modCount!!
,用来确定列表的容量是否够用,之前数组大小为size,所以这里是size+1大小,方法实现为:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
里边有一个calculateCapacity(elementData, minCapacity)
,具体代码为:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
如果elementData 是一个空的,那么返回DEFAULT_CAPACITY和minCapacity里最大的,DEFAULT_CAPACITY为10,如果是数组为空的,minCapacity就是size+1,也就是1,所以返回10,也就是说空的数组会被初始化为一个容量大小为10的数组。如果elementData 不是空的,返回实际minCapacity的大小。
回到ensureCapacityInternal方法里的 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
方法,ensureExplicitCapacity方法的实现代码:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount是父类AbstractList中的属性,表示该列表的结构被修改的次数,修改列表结构就是改变列表大小等操作,可能会造成迭代列表时产生错误的结果,modCount就是用来记录修改列表结构的次数的。
grow方法,就是用来真正扩容的方法了。实现代码为:
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);
}
首先获取原数组长度:oldCapacity ,然后将数组扩大原来容量的一半再加上原来的容量,如果newCapacity 的大小要比传过来的参数minCapacity小,这个新的长度newCapacity 就等于minCapacity,如果newCapacity 大于了数组的最大的长度,走hugeCapacity方法,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;
}
最后elementData = Arrays.copyOf(elementData, newCapacity);
复制数组elementData为所需容量大小 。
回到public boolean add(E e)
方法的第二行代码:elementData[size++] = e;
进行赋值操作,返回true,add方法添加元素完成。
public void add(int index, E element)方法
在指定位置添加元素,源码为:
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++;
}
其中rangeCheckForAdd(index);
是检查index是否超出了列表的边界,具体实现为:
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
如果超出了,抛出异常。
下一行代码:ensureCapacityInternal(size + 1); // Increments modCount!!
已经说过了,确定增加容量大小。
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
这里复制数组,以插入元素,但是要将index之后的元素都往后移一位。然后就是插入元素,增加size的值。
public E 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;
}
第一步:rangeCheck(index);
检查边界,如果超出边界,则抛出异常。
第二步:modCount++;
记录修改次数。
第三步:E oldValue = elementData(index);
记录要删除位置的值。
第四步:int numMoved = size - index - 1;
记录删除元素后,后续元素要移动的步数numMoved
第五步:使用 System.arraycopy复制数组。
第六步:elementData[--size] = null;
这里将移动后多余的那个空位置为null,方便GC回收,节省内存空间。
最后返回删除的值。
** public boolean 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;
}
第一行:if (o == null) {
,如果o是null,可以得知,ArrayList可以存放null元素。
如果o是null,遍历elementData,找到第一个为null的,删除,返回true。
如果o不是null,遍历数组,找到第一个和o匹配的elementData元素,删除。
**fastRemove(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
}
记录了修改次数,需要元素移动步数,然后复制数组,最后将最后元素赋值为null,便于垃圾回收。
其他的常用方法
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,便于垃圾回收。
set()方法:
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
检查边界,合法的话就赋新的值,返回旧的值。
get()方法:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
检查边界,合法的话,返回index位置的值。
indexOf()方法
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;
}
遍历元素,找到第一个符合的值所在的下表,如果没有,返回-1。