开篇寄语: 我们唯一有的是时间,成功就取决于我们怎么利用时间和它的副产品——闲暇时间。
总述: ArrayList是一个允许重复元素的集合类,内部通过数组来存储元素。
1. 类声明:
public class ArrayList
extends AbstractList
implements List
, RandomAccess, Cloneable, java.io.Serializable
很明显,ArrayList实现了四个接口,如果你打开这四个接口的话,你会发现除了List中定义了方法,其他三个接口都是空接口,没有任何方法定义。其实,这也是一种接口的使用方式,用空接口来做标志,表明某项特性或约束。
| RandomAccess表明ArrayList是可以随机访问的,也就是说ArrayList可以通过下标来访问
| Cloneable表明ArrayList是可以克隆的,这就意味着ArrayList对象是可以调用Object中的clone方法的(虽然clone方法是定义在Object中,但是只有实现Cloneable接口的类对象可以调用,否则会报CloneNotSupportedException);
| Serializable表明ArrayList是可被序列化的。
下面,来看看ArrayList的一些核心方法。
2. 构造器
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
this(10);
}
既然ArrayList内部是用数组来实现元素存储的,那么构造器必然需要对数组成员对象实例化。通过源代码,你会发现无参构造器的默认数组初始大小是10(这个经常有面试官会问,面试官其实是想知道你是否看过ArrayList源码,从而判断你对Java集合框架的使用深度,所以请记住吧)。
3. 添加元素
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
如果你看看add的系列方法,从源码中你会发现,凡是add系列方法,第一步都是会调用ensureCapacity方法,这个方法是用于实现ArrayList的可扩展数组能力的关键方法,它会计算比较当前元素数+1(即将被插入的元素)后的数组元素个数和数组长度,如果前者大于后者,则意味着需要扩展当前数组大小,从源代码中可以看出,这个时候会将数组大小扩展为插入前数组大小的1.5倍。
4. 删除元素
public E remove(int index) {
RangeCheck(index);
modCount++;
E oldValue = (E) 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;
}
如果,您足够细心和好奇的话,会对add/remove系列方法中对于成员变量modCount的操作感到疑惑。甚至在ensureCapacity方法中,也会对先modCount做++操作,那么modCount是用来做什么的呢?其实这个modCount是用于并发控制的。让我们设想一种场景:当一个线程对一个ArrayList对象进行迭代操作时,另外一个线程在对该ArrayList对象做add操作,那么这个时候新增的元素是不能被迭代器可见的,你可能会想不可见就不可见呗,应该也不会出什么大问题吧?的确是这样,这其实也是后续博客会介绍的CopyOnWriteList设计思想,只保证元素的最终可见性,而不保证该可见性的实时性。那么,如果另一个线程在对该ArrayList进行remove操作呢,这个时候可能会发生什么呢?让我们想象极端情况,如果迭代器操作到了最后一个元素,而且其hasNext方法刚刚执行完毕,这个时候线程上下文切换,导致了该元素被remove掉,继续上下文切换,执行迭代器的next方法,这个时候很明显会发生数组下标越界的异常。为了尽早的发现这种并发导致的异常,在创建ArrayList迭代器时,会保存该modCount的一个副本,如果在迭代过程中发现该副本的值和当前modCount值不相同,则表明存在并发修改ArrayList大小的操作发生,抛出ConcurrentModificationException。那么,现在可以明白,ArrayList用抛出异常的手段既保证了元素的可见性,又保证了这种可见性的实时性,当然这也了牺牲并发性。在实际应用中,如果只需要保证元素可见性,而不需要精确的保证实时性,则可以使用CopyOnWriteList,来享受并发带来的效率提升。典型的应用场景有监听器和过滤器列表的维护,如MINA框架中就是这样做的。
5. 克隆方法
public Object clone() {
try {
ArrayList
v = (ArrayList
) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
从方法实现可以看出,只是对ArrayList对象本身进行了深度克隆,并没有对底层元素也做深度克隆;如果要完整的深度克隆,则要保证其中的所有元素对象都要实现Clonable接口,并且覆盖实现克隆方法。
6. 迭代器
private class Itr implements Iterator
{
int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
集合框架中的迭代器都是以成员内部类方式存在的。注意checkForComodification方法,modCount的作用就在这个方法中体现。
7. 时间复杂度
| 插入指定元素的时间复杂度很明显是O(1);插入指定索引位置的时间复杂度为O(N),主要是要移动元素位置。
| 删除操作的时间复杂度为O(N),因为删除后都需要移动元素。
| 查询元素的时间复杂度为O(1),因为可以根据索引随机访问元素。