第三章 JAVA集合之ArrayList源码浅析

         一.概括

               ArrayList集合是我们工作中最常用的集合之一。ArrayList等同于一个动态的数组,动态的数组顾名思义就是可以自动扩容的数组,而不需要我们手动的去调整数组的大小。ArrayList是对数组进行了封装,而且还对增加了一些对这个数组进行操作的方法。

         

         二.ArrayList源码解析

         1.ArrayList实现接口和属性               

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * ArrayList用来存取数值的数组,
     * 对ArrayList的操作其实就是对这个数据的操作
     */
    private transient Object[] elementData;

    /**
     * 代表ArrayList存储元素的个数,注意size不等于elementData的长度
     *
     * @serial
     */
    private int size;
        ArrayList实现了RandomAccess接口,RandomAccess接口里面是没有任何方法的,实现RandomAccess支持快速随机访问,这样ArrayList使用for循环遍历元素要比使用迭代器遍历元素要快。ArrayList实现了Cloneable接口,ArrayList支持浅拷贝。ArrayList实现了Serializable接口,Serializable支持序列化。

       

        2.ArrayList构造方法       

/**
     *带有初始值的构造方法,
     *initialCapacity这个值是设置数组的大小
     * 
     */
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    /**
     * 不带参数的构造方法,
	 * ArrayList在初始化的时候会给数组的长度设为10
     */
    public ArrayList() {
        this(10);
    }

    /**
     * 带有集合参数的构造方法
     * ArrayList会将集合类型的参数转变成数组
     * 再将数组中的元素复制到新的数组中,
	 * 再让elementData指向新的数据
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }
       

       3.ArrayList的其他常用方法

       3.1ArrayList扩容方法:ensureCapacity     

/**
     * 
     * 
     * 预先设置ArrayList的容量大小,如果传入的参数minCapacity小于
     * ArrayList中elementData数组的长度就不做改变
     * 如果minCapacity参数大于elementData数组的长度,就会对
     * 将elementData的大小设置为原来的1.5倍,从新设置的数组长度如果还小于参数minCapacity
	 * 那么数组长度就直接变为minCapacity
     */
    public void ensureCapacity(int minCapacity) {
        if (minCapacity > 0)
            ensureCapacityInternal(minCapacity);
    }

    private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }


    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;


    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
		//新的数组长度是原数组长度的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }


	private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }<em>
</em>

    在要对ArrayList进行插入操作的时候,一般先要调用ensureCapacity方法,确定ArrayList是否能需要扩容。


    3.2ArrayList的添加和删除方法

/**
     * 向ArrayList添加元素,在添加元素之前会先判断ArrayList是否需要扩容
	 * 元素e将被添加在ArrayList的最末尾的位置
     */
    public boolean add(E e) {
		//将会用size + 1和elementData.length比较大小,如果size + 1大于elementData.length
		//将会对数组elementData进行扩容
        ensureCapacityInternal(size + 1);  
		//这句话可以分解为elementData[size] = e;size++这两步
        elementData[size++] = e;
        return true;
    }

    /**
	 *在指定位置添加元素
	 */
    public void add(int index, E element) {
		//判断index是否小于0或大于size,如果满足将抛出异常
        rangeCheckForAdd(index);
        //判断ArrayList是否需要扩容
        ensureCapacityInternal(size + 1); 
		/*
		 *将数组elementData从下标index开始的元素,长度为size - index(即index到size之间的元素)复制到
		 *数组elementData以index+1开始的位置,再将element放在数组index的位置
		 */
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

    /**
     *按下标移除元素
     */
    public E remove(int index) {
		//先判断index是否大于等于size,如果大于等于将报异常
        rangeCheck(index);

        modCount++;
		//防止数组index下标位置所指向的内存在移动元素的时候被占用
        E oldValue = elementData(index);
        //需要在elementData数组中移动元素的长度
        int numMoved = size - index - 1;
        if (numMoved > 0)
			//将elementData从下标index+1开始的元素到,长度为numMoved,移除到elementData的下标为index开始的地方
		    //这样就能将elementData中下标为index的元素覆盖掉
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work

        return oldValue;
    }

    /**     
     * 按元素内容移除
     * 先找到元素在数组中的下标,再按下标移除
     * 
     */
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
				    /*
					 *按下标移除元素,为什么不直接调用remove,而要调用fastRemove方法
					 *fastRemove方法和remove方法比起来少了边界的判断和旧元素的赋值,更快速
					 */
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * 快速移除元素,是一个私有方法,和remove方法比起来少了对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; // Let gc do its work
    }

    /**
     * 清空ArrayList,将elementData中的所有元素设置为null
     * 
     */
    public void clear() {
        modCount++;

        // Let gc do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

    /**
     * 向ArrayList增加一个集合
	 * 先将集合转变为数组,在将数组中的元素复制到elementData中
     */
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew); 
		//将a中所有的元素复制到elementData中size之后的位置
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
       ArrayList中元素的添加和删除操作都得依赖 System的arraycopy方法, System.arraycopy(a, 0, elementData, size, numNew);的意思是将数组a中从下标0开始,到0+numNew=numNew之间的元素移动到elementData数组中,但存放的位置是在size之后的,这样说应该能懂这个方法是干什么的了,这个操作是比较花时间的,所以对于添加和删除操作比较的频繁的时候,LinkedList是要好于ArrayList的,所以平时不要一味的依赖ArrayList。



    3.3ArrayList一些简易方法    

/**
     * 
     *获取ArrayList的大小,即获取size属性就可以了
     */
    public int size() {
        return size;
    }

    /**
     * 判断ArrayList是否为空
     */
    public boolean isEmpty() {
        return size == 0;
    }

   /**
	*判断ArrayList是否包含某一个元素
	*/
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    /**
     * 返回元素在ArrayList中的首位置,
	 * 如果ArrayList中没有这个元素,就返回-1
     */
    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;
    }

    /**
     * 返回元素在ArrayList中的最后出现的位置,
	 * 如果ArrayList中没有这个元素,就返回-1
     */
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

    /**
     * 对ArrayList进行浅拷贝
     */
    public Object clone() {
        try {
            @SuppressWarnings("unchecked")
                ArrayList<E> v = (ArrayList<E>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            throw new InternalError();
        }
    }

    /**
     * 将ArrayList转变为数组
     * 
     */
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    /**
     * 将ArrayList转变为参数数组a并返回
     */
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

    // Positional Access Operations

    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

    /**
     *按下标返回ArrayList中的元素
     */
    public E get(int index) {
		//检查index是否大于等于size,如果大于等于将报数组越界异常
        rangeCheck(index);

        return elementData(index);
    }

    /**
	 *按下标设置元素
	 */
    public E set(int index, E element) {
		//检查index是否大于等于size,如果大于等于将报数组越界异常
        rangeCheck(index);

        E oldValue = elementData(index);
		//下标index将指向新的元素
        elementData[index] = element;
        return oldValue;
    }

       还有一些其他的方法就不在这里一一列出来了,大家感兴趣的可以自己去看看


      三.总结

           1.如果在声明ArrayList的时候不设初始值,那么ArrayList会默认设置一个容量为10的数组,但是ArrayList的大小还是为0的

           2.ArrayList可以看成是一个动态的数组,相比较与数组来说,ArrayList可以用ensureCapacityInternal方法自动扩容,在向ArrayList添加元素的时候,ArrayList会先检测现在数组的容量是否足够,若不够,ArrayList会将数组的容量扩大为原来的2.5倍,如果还不够,就用传进来的参数作为数组的容量。如果我们在知道存储元素多少的时候,尽量给ArrayList设定一个初始容量,这样就可以减少ArrayList的自动扩容,减少数组元素的移动来提高程序的性能。

           3.ArrayList在增加或删除元素的时候,都需要将数组里面的元素移动到另一个数组中,这是非常耗时间的,所以遇到需要对元素进行频繁的删除和添加的集合时,这时候选用LinkedList要比ArrayList好很多,如果遇到在集合中查找元素比较多的操作时,ArrayList又是一个不错的选择,因为ArrayList直接可以用下标就可以获取到元素。

          4.在研读ArrayList源码的时候要注意ensureCapacityInternal扩容方法和System.arraycopy(original, 0, copy, 0,length)方法。


       参考文章:http://www.cnblogs.com/ITtangtang/p/3948555.html



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值