Java集合框架源码分析之ArrayList

开篇寄语: 我们唯一有的是时间,成功就取决于我们怎么利用时间和它的副产品——闲暇时间。


总述: 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),因为可以根据索引随机访问元素。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值