ArrayList扩容机制

86 阅读3分钟

ArrayList扩容机制

3种构造方法

  • 无参构造

    // 前置参数
    // ArrayList里缓存数据的数组
    transient Object[] elementData;
    // 空数组,用来无参构造创建一个空的数组对象
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    public ArrayList() {
        this.element = DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    }
    
  • 有参构造

    • 参数为Collection的子类

      // 前置参数
      // 空数组对象
      private static final Object[] EMPTY_ELEMENTDATA = {};
      public ArrayList(Collection<? extends E> c) {
          Object[] a = c.toArray();
          if((size = a.length) != 0) {
              if(a.getClass == ArrayList.class) {
                  element = a;
              }else {
                  elment = Arrays.copyOf(a, a.length, Object.class);
              }
          }else {
              elment = EMPTY_ELEMENTDATA;
          }
      }
      
    • 参数为int

      // 前置参数
      // 空数组对象
      private static final Object[] EMPTY_ELEMENTDATA = {};
      public ArrayList(int initialCapacity) {
          if(initialCapacity > 0) {
              this.element = new Object[initialCapacity];
          }else if(initialCapacity == 0) {
              this.element = EMPTY_ELEMENTDATA;
          }else {
              throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
          }
      }
      

    从上面的三种构造方法中可以了解到,创建一个ArrayList对象其实就是创建了一个数组对象,都知道数组一旦创建长度都是固定的,那么ArrayList的扩容机制又是怎么扩容的呢?

    ArrayList扩容

    首先先来看下add(E e)方法

    // 前置参数
    private int size;
    transient Object[] elementData;
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    private static final int DEFAULT_CAPACITY = 10;
    protected transient int modCount = 0;
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    ​
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    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;
        }
    

    我们一个个方法来看:

    1. ensureCapacityInternal这个方法很简单主要是为了确定初始化的数组长度是多少
    2. calculateCapacity则是如果ArrayList对象是通过空参构造创建的,那么数组的长度就是10,否则就是当前数组长度+1
    3. ensureExplicitCapacity判断当前的数组容量elementData.length是否满足这次新增的数组长度minCapacity如果不满足则需要进行扩容;而modCount用来统计队列发生的变化次数,modCount作用
    4. grow首先将原来的数组长度进行1.5倍扩容,如果扩容后的长度(newCapacity)还是小于新增元素长度(minCapacity)则直接使用新增后的数组长度,然后判断扩容后的长度(newCapacity)是否大于最大的数组长度,如果大于则需要调用hugeCapacity进行数组长度最大扩容
    5. hugeCapacity所谓数组长度最大化扩容就是判断扩容后的长度是否已经超过MAX_ARRAY_SIZE,如果已经超过则使用Integer最大值

    总结: 判断element是否是空数组,如果是空数组那么数组长度就是默认长度10,如果不是空数组,则判断当前数组长度是否小于新增数组长度,如果小于则调用grow()方法进行扩容,将原来的数组长度右移1(增加1.5左右),然后再判断一下扩容后的长度是否还小于新增数组长度(创建ArrayList空对象,数组长度为0,即使右移后也还是0),小于则将新增数组长度赋值给扩容数组长度,扩容后的数组长度和ArrayListMAX_ARRAY_SIZE比较如果大于则扩容长度扩容至Integer最大值

其他思考

  1. 为什么会有两个空数组对象EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA

    答: jdk1.7时创建容量为0的ArrayList对象时,会直接将EMPTY_ELEMENTDATA值赋值给element,如果一个应用中有多个ArrayList空对象就会有多个空数组,造成很多不必要的浪费.EMPTY_ELEMENTDATA是为了优化创建ArrayList空实例时产生不必要的空数组,使得所有ArrayList空实例都指向同一个空数组。DEFAULTCAPACITY_EMPTY_ELEMENTDATA是为了确保无参构成函数创建的实例在添加第一个元素时,最小的容量是默认大小10。

参考:
  1. arraylist等记录修改次数modCount有什么作用?
  2. ArrayList源码中EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别