ArrayList源码分析及高频面试问题答案

一,首先看ArrayList 中的两个重要的变量

transient Object[] elementData; //数组容量
private int size; //实际存的元素个数

下文将会频繁提及它们。

二,看下构造方法

构造方法1:

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

传入初始值,则直接生成一个相应长度Object[]数组,此时size值是多少呢?
由于没有赋值,则仍旧使用实例化时的值,即0。

构造方法2:

 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 ......
 public ArrayList() {
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
 }
   

将elementData赋值为一个空的object[]数组,此时size=0;

构造方法3:

public ArrayList(Collection<? extends E> 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;
        }
    }

传入参数为一个集合对象,首先将集合转化为数组,然后使用系统函数将该数组copy到elementData,即完成了赋值。此时size=elementData.length 即集合对象的长度。

下面看下add方法:

private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public boolean add(E e) {
   ensureCapacityInternal(size + 1);  // Increments modCount!!
   elementData[size++] = e;
   return true;
}
private void ensureCapacityInternal(int minCapacity) {
  ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
   if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

增加一个元素,首先确保容量正确。
将当前的size+1传给calculateCapacity方法,如果数组中不存在元素,取size+1与10相比的最大值,返回。否则直接返回size+1。
然后size+1的值传入ensureExplicitCapacity 方法。如果该值大于实际目前数组中元素个数,说明当前空间不够了,需要扩容。否则除了增加modCount值后,不进行更多操作。modCount的作用我们稍后再说。
我们看下grow方法

 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    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;
}

首先对现有容量增加50%,去其与所需容量的最大值,如果该值大于Integer.MAX_VALUE - 8,则返回Integer.MAX_VALUE。所以一般所需容量小于现有容量的1.5倍,则使用1.5倍值,否则使用所需容量值,来新建数组并将旧数据copy过来。

下面看下get 方法:

 public E get(int index) {
    rangeCheck(index);
      return elementData(index);
  }
private void rangeCheck(int index) {
    if (index < 0 || index >= this.size)
         throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
 }

首先进行下标越界检查,然后通过下标返回该元素。

下面看下set方法:

public E set(int index, E element) {
  rangeCheck(index);
  E oldValue = elementData(index);
  elementData[index] = element;
  return oldValue;
}

可以看到是将index对应的值修改后,返回旧值。

remove方法

public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    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
    return oldValue;
}

可以看到是使用了 System.arraycopy将index后面的元素都copy往前移一位,将旧值返回。
可见删除元素代价较大,而且元素越靠前,所要移动的数据就越多。

在指定位置新增元素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++;
    }

可见是通过System.arraycopy,将数据进行copy,代价较高。

总结如下,实例化:用无参方法初始化时,只分配了一个空数组。使用容量参数初始化时,分配一个参数大小的数组。使用集合作为参数初始化时,将集合转为数组,copy到新数组。

面试中经常问到的相关问题:

扩容机制是什么?
当数组元素为0时,第一次添加元素,容量变为10。此后add元素后所需容量大于现有容量时,进行扩容,扩容为1.5倍,如果还不够,则使用当前元素总数为数组长度。

ArrayList的优缺点是什么?
优点:可以根据下标快速找到元素(跟LinkedList相比),可以自动扩容(跟数组相比)。
缺点:1.指定下标增删数据,需要使用system.arraycopy 本地方法将该下标以后的数据进行大面积copy,效率较低。2.数组中可能会存在空元素,造成一定的空间浪费。

ArrayList使用场景有哪些?
适用于需要根据下标查询的可变长数组的情况。避免在需要对元素大量增删情况下使用(对列尾部增删无限制)。

以上。

你可能感兴趣的:(java,源码分析)