ArrayList 对象继承了AbstractList对象,这就是说,ArrayList可以使用迭代器来操作,但是有一点要注意,上一次我们看AbstractList源代码时,知道这个迭代器是快速失败的,也就是说他记录了修改的次数,因此在实现这个ArrayList的添加操作时,我们也需要随时更新这个操作记录。也就是如下这个变量。
protected transient int modCount = 0;
现在从头开始看ArrayList对象的使用。
第一步是构造函数,我们经常使用如下语句来初始化对象。
List<Object> list = new ArrayList<Object>();
实际上调用的构造函数如下:
public ArrayList() {
this(10);
}
这里使用了另外一个构造函数,
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
这个构造函数就是初始化了一个Object数组。并在初始化之前检查了参数范围。我们平时使用的时候,默认都是10.
现在看一下最常用的添加元素操作,源代码如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这里添加逻辑很简单,就是在数组末尾添加了这个元素。但是添加之前,有一个ensureCapacityInternal操作,这个操作就很重要了。我们看看做什么的?
private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
这里首先给父类的modCount变量加1,就是说这个容器又多了一个操作,这个主要用于检查多线程环境来使用的。
另外,这里JDK代码中注释了一句overflow-conscious code,检查了,如果数组末尾的这个下标已经超出了数组长度的话,那么就需要扩展数组的长度了。这里使用了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);
}
这个方法逻辑就是扩展数组的长度,长度扩展逻辑是旧的容量除2,再加上以前的容量,换句话说就是一半一半增加的。这里又判断,如果新的容量还是不够的话,那么就直接将最新的数组下标值赋值给数组长度,再进一步检查,新的容量是否比Integer.MAX_VALUE-8还要大,这里为什么是这个数值,JDK的源码中注释了如下说明:
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
是说有一些JVM会保存一些头字段存放在数组中。如果清理比这个更大的数组,就会导致内存溢出。所以这里是JVM的限制了。
所以整个添加操作到这里基本就结束了。
看一下删除操作:
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; // Let gc do its work
return oldValue;
}
这里简单说明下删除的逻辑:
这里简单说一句,将变量置为null,我以前一直以为是纯属装B的工作,变量过了作用域自然会被GC清理,但是这里却也这么做了,究其原因,是因为这是数组,不是一个简单的变量。
说明一下Vector对象的实现,这个对象也是实现了AbstractList接口,实现和ArrayList一样,但是有一点不同,就是Vector对象中所有的操作接口都是用了synchronized关键字,也就是说每个操作都给Vector对象实例加锁了。这个和StringBuilder&StringBuffer的差异是一样的。