jdk8--ArrayList底层源码分析

ArrayList是我们常见的一种Java集合,在日常的编程和面试中出境频率非常之高,今天结合源码总结一下ArrayList。

一、继承的父类与实现的接口。

首先,ArrayList继承了 AbstractList类,实现了List, RandomAccess, Cloneable, java.io.Serializable接口,即:

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

二、属性

接下来我们看一下ArrayList有哪些属性,因为下文中的很多方法都会用到这些属性。

1、serialVersionUID是用来验证版本一致性的字段。我们将一个类的二进制字节序列转为java对象,也就是反序列化时,JVM会把传进来的二进制字节流中的serialVersionUID和本地相应的实体或对象的serialVersionUID进行比较,如果相同,则认为两个类是一致的,可以进行反序列化,否则就会出现版本不一致的反序列化异常。

     private static final long serialVersionUID = 8683452581122892189L;

2、elementData:元素存放的数组

 /**
  * The array buffer into which the elements of the ArrayList are stored.
  * The capacity of the ArrayList is the length of this array buffer. Any
  * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
  * will be expanded to DEFAULT_CAPACITY when the first element is added.
  */
   transient Object[] elementData

3、DEFAULT_CAPACITY :默认的初始容量

    private static final int DEFAULT_CAPACITY = 10;

4、EMPTY_ELEMENTDATA :如果在构造集合时传入了初始容量,会判断这个传入的值,如果大于0,就new一个新的Object数组,如果等于0,就直接设elementData为EMPTY_ELEMENTDATA。

    private static final Object[] EMPTY_ELEMENTDATA = {};

          5、DEFAULTCAPACITY_EMPTY_ELEMENTDATA:如果在构造集合时不传入初始容量,就使用默认容量,并设置elementData为DEFAULTCAPACITY_EMPTY_ELEMENTDATA。当第一个元素被添加时该数组扩容为默认初始容量10。

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

         6、size:集合所包含的元素的个数

    private int size;

三、构造函数

       1、无参构造函数。

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

       2、构造指定初始容量的ArrayList容器。

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
        }
    }

          3、构造一个包含指定集合的元素的ArrayList,它们按照集合的迭代器返回的顺序排列。代码中有一句注释      //c.toArray might (incorrectly) not return Object[] (see 6260652),这是官方的一个bug。意思是说collection.toArray()理论上应该返回Object[]。然而使用Arrays.asList得到的list,其toArray方法返回的数组却不一定是Object[]类型的,而是返回它本来的类型。例如Arrays.asList(String)得到的list,使用toArray会得到String数组。如果向这个数组中存储非String类型的元素,就会抛出ArrayStoreException异常。

    public ArrayList(Collection c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

四、添加元素的方法与扩容机制。

1、add(E e),添加单个元素

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

2、add(int index,E element)在指定的位置插入元素。

    public void add(int index, E element) {
        rangeCheckForAdd(index);
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

其实方法就是在底层数组中加一的位置新增一个元素而已,没有我们想的那么神秘。但是新增元素时我们要确定数组的容量是够的,在此方法中调用了ensureCapacityInternal(size+1)方法:

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
             //比较一下默认容量和当前数组增加元素后的实际容量的关系,返回两者中较大的一个,此处的minCapacity不一定为size+1,因为addAll也会调用这个方法,addAll会传入多个元素
             return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

     private void ensureExplicitCapacity(int minCapacity) {
        //此列表在结构上被修改的次数
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            //如果新增元素插入后数组中的元素数量将大于数组的长度则扩容
            grow(minCapacity);
     }

扩容函数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);
 }

扩容规则如下:首先拟定新数组的长度为原长度的1.5倍,为什么要选择1.5倍呢,如果一次扩容扩得太大,必然会造成内存空间的浪费,如果一次性扩容不够,那么下一次扩容必将很快到来,会降低程序的运行效率。综合时间和空间上得考虑后,选择合理的数值。在第一次容量增加后会把扩容后得数值和将插入元素后的数值作比较,如果容量还是不够则会把插入元素后的数组长度作为扩容的目标。如果二次扩容的数组长度超过了MAX_ARRAY_SIZE(private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8),则会调用hugeCapacity(minCapacity)函数,hugeCapacity函数的具体内容如下:

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

五、移除元素。

1、public E remove remove(int index):

    public E remove(int index) {
        //检查index有没有越界
        rangeCheck(index);
        //修改次数增加1
        modCount++;
        E oldValue = elementData(index);
        //该位置后的元素个数 
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //arraycopy的5个参数含义为原数组、指定原数组开始复制的位置、目标数组、复制过来的元素在目标数组中的首地址、要copy的元素的个数
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //移除指定个数的元素后,数组元素最后一位置为null,方便gc回收
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

2、public boolean remove(Object),此方法注意区分元素是否为null,两种判别方式是不一样的。

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    //如果要删除的元素和容器中该位置的元素匹配,调用fastRemove方法
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

      //还是利用了arrayCopy的方法
     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; // clear to let GC do its work
    }

3、public boolean removeAll(Collection c) 

    public boolean removeAll(Collection c) {
        //检查传入的元素是否为空,为空则抛出NullPointerException异常
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }


    private boolean batchRemove(Collection c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    //此时只记录不在传入集合中的元素
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            //若中途抛出异常,异常的原因在上文中有提到过
            if (r != size) {
                //确保抛出异常前的部分已经完成期望的操作,未被便利的部分接到后面
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

   删除元素尽量使用指定下标的方法,性能好。每次删除都会进行数组移动(除非删除最后一位),如果频繁删除元素,请使用LinkedList。

你可能感兴趣的:(Java)