ArrayList 扩容机制完全解析-简单易懂

ps:如果不想看分析,可以直接跳到末尾看扩容结论)

ArrayList 底层就是使用数组实现的,从名字可以瞅出来对吧,再者它是支持泛型的,所以它的内部使用的是 Object 类型的数组,名为 elementData,这样可以兼容各种数据类型;

transient Object[] elementData;

来看几个与 elementData 数组相关的内部属性:

//一个空数组:elementData 默认初始化会用到这家伙
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final int DEFAULT_CAPACITY = 10;//默认容量值(jdk8)
private int size;//用于表示 elementData 数组中被使用的大小

再看下 ArrayList 的初始化容量设置:

//1.根据上面的属性介绍可以看出,不指定容量的 ArrayList 创建出来,只是一个空数组
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public ArrayList(int initialCapacity) {
      //如果指定了容量,那么 elementData 的大小就会是指定的大小
      if (initialCapacity > 0) {
          this.elementData = new Object[initialCapacity];
      } else if (initialCapacity == 0) {
           //2.如果指定的容量为 0,elementData 同样会被赋值为一个空数组
          this.elementData = EMPTY_ELEMENTDATA;
      } else {
          throw new IllegalArgumentException("Illegal Capacity: "+
                                             initialCapacity);
      }
  }

总结:从上面的代码和注释可以总结出,在不指定容量大小或者容量指定为 0 的这两种情况下,ArrayList 中存储数据的数组 elementData 都会被赋值为一个空数组,容量不是默认容量,而是 0;

ok,准备工作完毕,下面开始由一个 add(E e) 添加数据的方法,引出 ArrayList 的扩容机制,从 add(E e) 源码开始吧:

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

在 add() 方法中调用了 ensureCapacityInternal() 方法,我们需要注意的就是调用这个方法时传入的参数,它是 size+1 ,前面已经提到了 size ,它是 elementData 中存在的有效数据个数,之所以传递 size+1 ,是因为它是调用 add() 方法后,数据的总数,也是 elementData 数组必须要达到的最小容量,进入 ensureCapacityInternal() 方法中瞅瞅:

//留意:minCapacity 的含义就是 elementData 的大小必须要大于等于它,
//     不然就要扩容,后续介绍的方法中,此次名称的参数含义也是如此
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 作用:此方法的作用就是根据 elementData 和 minCapacity 再次确定
//       下 minCapacity 的大小
private static int calculateCapacity(Object[] elementData, int minCapacity) {
      //判断 elementData 是否为 [],如果为空,也能说明 add() 方法也是
      //第一次被调用,那么 minCapacity 的值就是 1,最后的返回值就是
      //默认容量值 10,这就是默认容量值作用
      if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
          return Math.max(DEFAULT_CAPACITY, minCapacity);
      }
      //如果 elementData 不是 [],就直接返回原本dd minCapacity
      return minCapacity;
  }

上面的分析中,主要就是对 minCapacity 进行了一次重新的计算,前面也提到了它的意义,ArrayList 中的默认容量值也是在这里被使用到的,好了,对 minCapacity 进行了重新计算后,会将它传入 ensureExplicitCapacity() 方法中,继续往下看:

//建议:你瞧,这个参数也叫 minCapacity ,所以看这几个方法的时候,
//      可以直接将 minCapacity 看作是用在这几个方法直接的全局变量
private void ensureExplicitCapacity(int minCapacity) {
      modCount++;

      if (minCapacity - elementData.length > 0)
          grow(minCapacity);
}

上面的方法中判断了以 elementData 现在的容量是否能够容纳的下 minCapacity 个数据,如果能的话,扩容个锤子,直接返回到了 add() 方法中将数据添加到现在的 elementData 数组中了,如果不能,才会进入 grow() 方法,这个方法就是真正的扩容方法:

private void grow(int minCapacity) {
      //1.首先取出 elementData 数组的长度
      int oldCapacity = elementData.length;
      //2.以位运算的方式计算得到 elementData 容量的 1.5 倍
      //重点:这里就是 ArrayList 普遍扩容时的容量变化
      int newCapacity = oldCapacity + (oldCapacity >> 1);
      //3.下述重点分析
      if (newCapacity - minCapacity < 0)
          newCapacity = minCapacity;
      //补充:这里是为了应对当 elementData 很大的时候,扩容引起的内存变化
      //      也会非常大,容易导致 OOM 出现而设置的
      if (newCapacity - MAX_ARRAY_SIZE > 0)
          newCapacity = hugeCapacity(minCapacity);

      //到这里,新数组的容量就已经定好了,使用工具方法进行了扩容赋值操作
      elementData = Arrays.copyOf(elementData, newCapacity);
  }

==重点分析(3):==这里的判断是应对特殊情况的,因为一般情况下,newCapacity 是不会小于 minCapacity 的,这里针对的是两种情况,一种是 elementData 被赋予的是空数组,那么此时的 newCapacity 为 0,而 minCapacity 为 10,很显然,需要使用 10 来进行扩容,另外一种情况是一个细节,就是创建 ArrayList 的时候,指定的容量是 1,那么此时的 newCapacity 虽然是经过 oldCapacity 运算过的,但还是 1,而 minCapacity 是 2,所以会使用 minCapacity 进行扩容;

总结:从以上的分析中,我们可以了解到的就是,不指定容量的情况下创建出来的 ArrayList 是一个 [] 空数组,当这种 ArrayList 被添加数据时,会直接将内部数组的大小扩容至默认的容量大小 10(jdk8),如果指定容量大小为 1,扩容之后会是 2,其他的普遍扩容,elementData 的容量都是原来的 1.5 倍大小,对于一些比较大的 ArrayList ,在扩容的时候,也进行了适当的限制,减小了由于数组过大而引发 OOM 出现的概率;

ps:关于 ArrayList 的最大容量,还没理解设计者的含义,欢迎讨论)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值