ArrayList底层实现源代码解析----add()方法如何实现及动态扩展情况

目录

一、ArrayList的构造函数:

二、ArrayList中的添加方法add()

add()方法:

ensureCapacityInternal():

ensureExplicitCapacity():

grow方法

Arrays.copyOf() :

三、常见面试题

1.那我们本身就有数组了,为什么要用ArrayList呢?

2.ArrayList是怎么实现的吧,为什么ArrayList就不用创建大小呢?

3.ArrayList怎样实现动态扩容的?

4.线程安全的List还有什么?


 

一、ArrayList的构造函数:

 /**
   * 初始容量大小
   */
  private static final int DEFAULT_CAPACITY = 10;
 /**
   * 空元素大小
   */
  private static final Object[] EMPTY_ELEMENTDATA = {};
 /**
   * 默认空元素大小
   */
  private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  
  transient Object[] elementData; 

  private int size;

  /**
   * 带初始容量的构造函数,
   */
  public ArrayList(int initialCapacity) {
      //初始化大小不为0,例:ArrayList(10)---initialCapacity=10
      if (initialCapacity > 0) {
          this.elementData = new Object[initialCapacity];
      //初始大小为0
      } else if (initialCapacity == 0) {
          this.elementData = EMPTY_ELEMENTDATA;
      } else {
          throw new IllegalArgumentException("Illegal Capacity: "+
                                             initialCapacity);
      }
  }

  /**
   * 默认构造函数,使用初始容量构造一个空的列表
   */
  public ArrayList() {
      this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  }

  /**
   * 构造包含指定的Collection元素的列表,这些元素利用该集合的迭代器按顺序返回
   * 如果指定的集合为null,则会抛出NullPointerException
   */
  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;
      }
  }

以无参构造器方法创建的ArrayList时,实际上上初始化赋值的是一个空数组;

二、ArrayList中的添加方法add()

add()方法:

/**
 * 将制定元素添加到此列表的尾部
 */
public boolean add(E e) {
    //添加元素之前,先调用ensureCapacityInternal方法
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //这里看到ArrayList添加元素的实质相当于为数组赋值
    elementData[size++] = e;
    return true;
}

ensureCapacityInternal():

//add方法首先调用了ensureCapacityInternal(size+1)
private void ensureCapacityInternal(int minCapacity) {
  //得到最小的扩容容量
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
         minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
     }
     ensureExplicitCapacity(minCapacity);
 }

当要add第一个元素的时候,minCapacity=1,在Math.max()方法比较后,minCapacity=10;

ensureExplicitCapacity():

//如果调用ensureCapacityInternal一定会执行ensureExplicitCapacity这个方法
private void ensureExplicitCapacity(int minCapacity) {
     modCount++;
  	 //判断是否需要扩容
     // overflow-conscious code
     if (minCapacity - elementData.length > 0)
         //调用grow方法进行扩容
         grow(minCapacity);
 }

(1)当add第一个元素到ArrayList,elementData.length为0(是一个空list),因为执行了ensureCapacityInternal()方法,所以minCapacity =10。此时,minCapacity - elementData.length>0成立,所以进入grow(minCapacity)方法;

(2)当add第二个元素到ArrayList,minCapacity=2,此时elementData.length(容量)在添加完第一个元素后扩容成10了。此时,minCapacity - elementData.length > 0不成立,所以不会进入grow(minCapacity)方法。

(3)添加第3、4…第10个元素时,依然不会进入grow(minCapacity)方法,数组数量都为10;直到添加第11个元素,minCapacity(为11)比elementData.length(为10)要大,进入grow方法进行扩容。

grow方法

/**
 * 要分配的最大数组大小
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
 * ArrayList扩容的核心方法
 */
private void grow(int minCapacity) {
     //旧的容量大小,newCapacity为新容量
     int oldCapacity = elementData.length;
     //将旧的容量右移一位,相当于oldCapacity/2
     //位运算的速度远远快于整除运算,整句运算式的结果就是将新的容量更新为旧的容量的1.5倍
     int newCapacity = oldCapacity + (oldCapacity >> 1);
     //然后检查新的容量是否大于最小需要的容量,若还是小于最小需要容量,那么就把最小需要的容量当做数组的新容量
     if (newCapacity - minCapacity < 0)
         newCapacity = minCapacity;
     //如果新容量大于MAX_ARRAY_SIZE ,执行hugeCapacity()方法来比较minCapacity和MAX_ARRAY_SIZE 
     //如果minCapacity大于最大容量,则新容量则为Integer.MAX_VALUE,否则,新容量大小为MAX_ARRAY_SIZE即为Integer.MAX_VALUE - 8
     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);
 }
  • 当add第一个元素的时候,oldCapacity=0,经比较后第一个if成立,newCapacity = minCapacity(为10)。但是第二个if不成立,不会调用hugeCapacity()方法。数组容量为10,add方法返回true,size+1。
  • 当add第11个元素会调用grow方法,newCapacity =15,比minCapacity =11要大,第一个if不成立。新容量没有大于数组最大size,不会调用hugeCapacity方法。数组容量扩容15,add方法返回true,size=11。

Arrays.copyOf() :

//以正确的顺序返回一个包含列表中所有元素的数组(从第一个到最后一个元素);返回的数组的运行时类型是指定数组的运行时类型。
public Object[] toArray() {
  //elementData:要复制的数组;size:要复制的长度
  return Arrays.copyOf(elementData, size);
}

三、常见面试题

1.那我们本身就有数组了,为什么要用ArrayList呢?

原生的数组会有一个特点:你在使用的时候必须要为它创建大小,而ArrayList不用。

2.ArrayList是怎么实现的吧,为什么ArrayList就不用创建大小呢?


其实是这样的,我看过源码。当我们new ArrayList()的时候,默认会有一个空的Object数组,大小为0。当我们第一次add添加数据的时候,会给这个数组初始化一个大小,这个大小默认值为10;

数组的大小是固定的,而ArrayList的大小是可变的;因为ArrayList是实现了动态扩容的,假设我们给定数组的大小是10,要往这个数组里边填充元素,我们只能添加10个元素。而ArrayList不一样,ArrayList我们在使用的时候可以往里边添加20个,30个,甚至更多的元素。

3.ArrayList怎样实现动态扩容的?

使用ArrayList在每一次add的时候,它都会先去计算这个数组够不够空间,如果空间是够的,那直接追加上去就好了。如果不够,那就得扩容。在源码里边,有个grow方法,每一次扩原来的1.5倍。比如说,初始化的值是10嘛。现在我第11个元素要进来了,发现这个数组的空间不够了,所以会扩到15;空间扩完容之后,会调用arraycopy来对数组进行拷贝。

4.线程安全的List还有什么?

java.util.concurrent包下还有一个类,叫做CopyOnWriteArrayList。CopyOnWriteArrayList是一个线程安全的List,底层是通过复制数组的方式来实现的;

add()方法其实他会加lock锁,然后会复制出一个新的数组,往新的数组里边add真正的元素,最后把array的指向改变为新的数组;CopyOnWriteArrayList是很耗费内存的,每次set()/add()都会复制一个数组出来,另外就是CopyOnWriteArrayList只能保证数据的最终一致性,不能保证数据的实时一致性。假设两个线程,线程A去读取CopyOnWriteArrayList的数据,还没读完,现在线程B把这个List给清空了,线程A此时还是可以把剩余的数据给读出来

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值