1

ArrayList作为java中常用的集合类型有哪些特点需要我们了解呢?本文将基于jdk1.8源码来一步步列出ArrayList有哪些需要令人注意的要点。

1 RandomAccess随机访问接口

ArrayList继承实现关系如下图:

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

从该图我们可以看出,ArrayList实现了RandmoAccess接口,RandmoAcess接口实现如下:

    public interface RandomAccess {
    }

RandmoAccess接口中实际上并无任何实现,该接口只是表示实现了该接口的类能够提供快速访问功能。

2 底层是数组类型

ArrayList实现了RandmoAccess接口代表能够进行快速访问,而ArrayList的快速访问功能实际上是靠数组实现的,下面是ArrayList中最重要的两个属性:

    //ArrayList实际存数据的地方
    transient Object[] elementData; 
    //ArrayList的大小
    private int size;

下面我们来看ArrayList的默认构造方法:

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

默认构造方法会将elementData属性赋为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,其中DEFAULTCAPACITY_EMPTY_ELEMENTDATA属性是一个空数组

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

接下来我们看ArrayList中的add和get方法:

    public boolean add(E e) {
        //确认是否扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    public E get(int index) {
        //检查是否越界
        rangeCheck(index);

        return elementData(index);
    }

可以看出Arraylist底层是通过数组来操作的。

3 惰性初始化

ArrayList使用默认构造器创建类时创建的是一个空的数组,那么ArrayList是如何能够使用add方法存数据的呢?我们可以详细看看add方法中的ensureCapacityInternal方法源码:

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //DEFAULT_CAPACITY常量数值是10
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        //记录修改次数
        modCount++;

        if (minCapacity - elementData.length > 0)
            //扩容方法
            grow(minCapacity);
    }
    

从以上源码可以看出当ArrayList为一个空数组时,会赋予一个默认的扩容大小10,然后再进扩容方法里扩容ArrayList,因此ArrayList使用默认构造器初始化时,不会立马初始化数组大小,而是等待调用add方法后才会进行初始化,且初始化大小为10。

4 位运算1.5倍扩容

我们可以查看ArrayList的grow方法来看看arrayList实际是如何扩容的:

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //位运算1.5倍扩容
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //判断minCapacity是否超出最大整数值
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        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;
    }

以上代码分为如下几个步骤:

  1. 首先用位运算对原数组进行1.5倍扩容得到新的扩容值newCapacity
  2. 再用新的扩容值newCapacity与实际传来的所需扩容值minCapacity进行比较,若newCapacity比minCapacity大则用newCapacity,反之则用minCapacity并判断minCapacity是否合法,这样得出的值为一个最终扩容值
  3. 最后用Arrays.copyOf方法对最终选出的扩容值进行扩容

艾尔雪枫
25 声望5 粉丝

Change every day!