ArrayList 源码的理解

ArrayList是常用的集合.平时用的时候做了些笔记。

一:首先看下ArrayList的继承基础:

public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable

ArrayList继承了AbstractList.实现了List.相当于动态数组,提供了增加,修改,删除,遍历功能。

实现了RandomAccess.提供了随机访问的功能。即get(i)方法。

实现了Cloneable,即可以被克隆。

实现了Serializable.可以被序列化。

二:分析下源码:

  1.有两种构造方法,List list=new ArrayList(); 默认创建大小为10的集合。
                    List list=new ArrayList(2);创建大小为2的集合。

        

 //ArrayList带容量大小的构造函数
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity]; //新建一个数组初始化elementData
    }
  
    //不带参数的构造函数
    public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;//使用空数组初始化 
                                         //elementData,EMPTY_ELEMENTDATA的值为10
    }


  2.每次集合添加对象时,会检查集合的大小是否满足。会调用ensureCapacity(size+1)方法。确保集合能容纳该元素。

//将e添加到ArrayList中
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }


    每次会添加1/2容量。源码即: int newCapacity = oldCapacity + (oldCapacity >> 1);

//给数组扩容,该方法是提供给外界调用的,是public的,真正扩容是在下面的private方法里
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != EMPTY_ELEMENTDATA)
            // any size if real element table
            ? 0
            // larger than default for empty table. It's already supposed to be
            // at default size.
            : DEFAULT_CAPACITY;
 
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
 
    private void ensureCapacityInternal(int minCapacity) {
	//如果是个空数组
        if (elementData == EMPTY_ELEMENTDATA) {
	    //取minCapacity和10的较大者
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
	//如果数组已经有数据了
        ensureExplicitCapacity(minCapacity);
    }


  3.ArrayList:添加一个元素到指定位置的时候,其后面的元素需要整体移动一位。
              删除一个元素时候,后面的元素同样需要整体向前移动一位。  所以删除和添加相对比较耗资源。

//将element添加到ArrayList的指定位置
    public void add(int index, E element) {
        rangeCheckForAdd(index);//检查下标是否符合要求
 
        ensureCapacityInternal(size + 1);  // Increments modCount!!
	//将index以及index之后的数据复制到index+1的位置往后,即从index开始向后挪了一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index); 
        elementData[index] = element; //然后在index处插入element
        size++;
    }


              然后查询就不一样了。因为插入的时候,已经标记了下标,所以可以直接根据下标来获取。所以速度是很快的。
              remove(Object obj)和remove(int one)相比较。后面一个速度快。因为前面一个首先需要遍历集合,找到该元素的下标,
              然后再调用remove(int one).
         4. contains(Object obj)这个方法也是需要遍历集合来查找是否有没有相等的元素。

 public boolean contains(Object o) {
        return indexOf(o) >= 0;
    } 



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;
    }

5.关于fail-fast机制。

  即A线程在通过迭代器来遍历集合x时候,如果B线程修改了集合x的结构,就会报ConcurrentModificationException 异常.

private class Itr implements Iterator {  
           int cursor;  
           int lastRet = -1;  
           int expectedModCount = ArrayList.this.modCount; //expectedModCount 不会进行修改 
      
           public boolean hasNext() {  
                return (this.cursor != ArrayList.this.size);  
            }  
      
            public E next() {  
                checkForComodification();  
                /** 省略此处代码 */  
            }  
      
            public void remove() {  
                if (this.lastRet < 0)  
                    throw new IllegalStateException();  
                checkForComodification();  
                /** 省略此处代码 */  
            }  
      
            final void checkForComodification() {  
                if (ArrayList.this.modCount == this.expectedModCount)  
                    return;  
                //expectedModCount 不会进行修改,如果modCount 的改变了,就会抛异常。
                throw new ConcurrentModificationException();  
            }  
}  

而每一次的修改ArrayList的结构,比如添加,删除等,都会调用ensureExplicitCapacity(int one);从而来改变modCount的值。

private void ensureExplicitCapacity(int paramInt) {  
        this.modCount += 1;    //修改modCount  
        /** 省略此处代码 */  
    }  

故这就验证了上面的结论。遍历的时候,另外的线程对该集合进行修改,就可能抛异常。

解决的方案:

 方案一:在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList,                 这样就可以解决。但是不推荐,因为增删造成的同步锁可能会阻塞遍历操作。

 方案二:使用CopyOnWriteArrayList来替换ArrayList。推荐使用该方案。

总体来说。随机访问是直接根据下标(和数组一样)来获取的。所以速度快。增加,删除需要整体移动。所以会比较耗资源。

所谓的效率比较,只要知道了原理,就好比较了

你可能感兴趣的:(集合)