ArrayList解析上篇

123 阅读4分钟

前阵子面试有问到ArrayList的源码有没有了解,在这里记录一下我对它的理解。

说到ArrayList的话,首先了解一下它的数据结构:底层还是数组

//transient是为了防止elementData序列化,将其生命周期放在内存,不放至硬盘中
//elementData是用来存储数据的数组,也就是arraylist的底层数据结构
transient Object[] elementData;

//DEFAULTCAPACITY_EMPTY_ELEMENTDATA用在没传入参数的时候
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//EMPTY_ELEMENTDATA 用来区别数组传参为0和没传参的情况
private static final Object[] EMPTY_ELEMENTDATA = {};

它和普通的数组有什么区别呢?为什么要推出这种ArrayList结构?

  讲到这,可以了解一下基本数组,首先它是在初始化时需要指定数组长度,
 存储的数据类型也只能是相同的一种如int/char等等,通过数组的下标索引可以快速访问某个位置上的数据。
而ArrayList与普通数组的区别就在于ArrayList是可以动态扩容的,
而且ArrayList继承于AbstratList类,
实现了RandomAccess, Cloneable, java.io.Serializable等接口,
RandomAccess提供了快速访问的功能。

接下来从ArrayList的构造方法说起!有三种构造方法,传参/不传参/传入一个集合


//先来说传参的情况
//传入一个数组初始容量
public ArrayList(int initialCapacity){
	//判断如果>0,则将存储数据的数组长度设为传入的大小
       if (initialCapacity > 0) {
           this.elementData = new Object[initialCapacity];
           //如果==0,则会将elementData数组设为默认的空数组。
           //上面介绍过了什么情况下用短的空数组,具体的后面再详细解释
       } else if (initialCapacity == 0) {
           this.elementData = EMPTY_ELEMENTDATA;
       } else {
       	//如果<0则抛异常
           throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
       }
   }
   

 //不传参的情况
 public ArrayList() {
 		//不传参的情况下,则直接将数组初始化为空
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
 
 //传入一个集合
 private int size;//数组的大小
 public ArrayList(Collection<? extends E> c) {
 		//将集合类型转换为数组
        elementData = c.toArray();
        //集合大小赋值并且判断是否为0
        if ((size = elementData.length) != 0) {
        //因为子类重写父类方法时,可以修改返回值类型,就有可能不是object类
        //而Arrays.copyOf是依赖于第一个参数的,所以要防止Arrays.copyOf不返回object类型
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
        //如果c集合的长度为0,则赋值空数
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    

//传入一个要插入的元素,默认插入的位置是集合尾部
 public boolean add(E e) {
       //插入数据之前会先调用方法判断是否需要扩容
       ensureCapacityInternal(size + 1);  // 添加操作会增加modcount!!
       //将数据插入到集合末尾
       elementData[size++] = e;
       //返回插入成功
       return true;
   }
   /**
   * minCapacity,最小容量
   */
   private void ensureCapacityInternal(int minCapacity) {
       ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
   }
   
   //接受到calculateCapacity计算得出的数组大小后
   private void ensureExplicitCapacity(int minCapacity) {
   	//增加会让modCount++
       //用于判断迭代遍历的时候判断是否遍历过程中是否发生变化
       //涉及到了fail-fast机制,如果遍历过程中modCount发生变化则会失败停止遍历
       modCount++;

       //如果最小的容量大小要大于数组的长度
       if (minCapacity - elementData.length > 0)
       	//则要进行扩容
           grow(minCapacity);
   }
   //用于计算最小容量
   private static int calculateCapacity(Object[] elementData, int minCapacity) {
   	//判断数组是否是默认空数组,也就是new ArrayList()创建的
       if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
       	//返回扩容后的大小
           return Math.max(DEFAULT_CAPACITY, minCapacity);
       }
       //如果是通过其他方法创建的ArrayList,如传入了参数,或者集合,则返回传入的大小
       return minCapacity;
   }
   
   //扩容方法
   private void grow(int minCapacity) {
       //记录扩容之前的容量
       int oldCapacity = elementData.length;
       //扩容后的新容量大小,旧容量+旧容量/2,也就是1.5倍扩容
       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) {
   	//如果最小容量<0,抛出异常
       if (minCapacity < 0) // overflow
           throw new OutOfMemoryError();
       //判断是否大于所能分配最大容量,是则返回Integer的最大值,否则返回能分配的最大容量
       return (minCapacity > MAX_ARRAY_SIZE) ?
           Integer.MAX_VALUE :
           MAX_ARRAY_SIZE;
   }
   

	//在指定位置插入指定元素
    public void add(int index, E element) {
    	//用于判断是否下标越界
        rangeCheckForAdd(index);
		
        //判断是否需要扩容
        ensureCapacityInternal(size + 1); 
        //调用System.arraycopy的方法,将index之后元素往后移动一位
        //elementData数组,index指定位置,elementData目标数组, index + 1开始位置, size - index 长度
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //在index指定位置上插入元素
        elementData[index] = element;
        //数组大小+1
        size++;
    }
    
    //方法用于判断是否下标越界,抛出异常,add/addAll用的版本
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    

	//添加一个集合的元素,默认从尾部开始
   public boolean addAll(Collection<? extends E> c) {
   	//类型转换成数组
       Object[] a = c.toArray();
       //新添加的数据的长度
       int numNew = a.length;
       //判断是否需要扩容
       ensureCapacityInternal(size + numNew);  // Increments modCount
       //从a数组的0位置开始复制长度为numNew的数据到elementData数组的size位置开始
       System.arraycopy(a, 0, elementData, size, numNew);
       //数组的实际长度
       size += numNew;
       //返回集合是否为0
       return numNew != 0;
   }
   
  //添加集合数据到指定位置
   public boolean addAll(int index, Collection<? extends E> c) {
   	//判断是否下标越界
       rangeCheckForAdd(index);
   	
       Object[] a = c.toArray();
       int numNew = a.length;
       ensureCapacityInternal(size + numNew);  // Increments modCount
   	
       //需要移动的长度
       int numMoved = size - index;
       //如果>0
       if (numMoved > 0)
       	//将elementData数组index开始的位置长度为numMoved的数据拷贝到elementData的index + numNew位置
           System.arraycopy(elementData, index, elementData, index + numNew,
                            numMoved);
   //将a数组的数据拷贝到elementData数组的index开始的位置,长度为numNew。
       System.arraycopy(a, 0, elementData, index, numNew);
       size += numNew;
       return numNew != 0;
   }