java8-ArrayList源码分析

文章目录

      • ArrayList
      • 1成员变量:
      • 2 构造函数
      • 3常用方法
          • 3.1 add
            • 3.1.1 扩容方法
          • 3.2 set
          • 3.3 get
          • 3.4 remove
          • 1 关于ArrayList的增删速度小于LinkedList,访问速度快于LinkedList
          • 2. 关于设置EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA
          • 3.关于Arrays.copyof() 和System.arraycopy方法

ArrayList

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

ArrayList,实现了List接口,底层结构是一个数组,怎么说呢,由于是一个数组,所以拥有数组的优点和缺点(访问快,增删慢)。而且它有下标,所以可以存放重复的元素。(这个应该是list接口的特点,而set容器由于没有下标,所以不能存放重复的元素。)

​ 它有一个默认容量为10,当它进行添加元素的时候,会先进行判断是否有空间添加元素,如果不够就进行扩容,扩容的大小是原来的1.5倍,(应该是效率比较高吧。对比HashMap中的加载因子为0.75)。它进行扩容的时候,采用的方法是Arrays.copyof,底层是System.arrayof方法。添加元素使用的的方法是System.arrayof方法。

1成员变量:

​ 当创建一个新的ArrayList时,它使用的是一个空的数组,直到第一个元素被添加它才会开辟一个默认大小的空间去存储元素。其中默认数组大小为10。

默认初始化容量是10
private static final int DEFAULT_CAPACITY = 10;

用来保存空实例的空数组
private static final Object[] EMPTY_ELEMENTDATA = {};

用于空实例,直到第一个元素被添加。为了区别EMPTY_ELEMENTDATA,当第一次添加元素之后,应该扩大多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

存储元素的object数组,当第一个元素被添加时,它会从DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个存储空实例的数组转变为这个数组,它的容量会变为默认的大小即10. 可以从这个变量的设置中看出ArrayList的结构实质为数组。
transient Object[] elementData; // non-private to simplify nested class access
数组已经使用的大小。
private int size;
设置最大能使用的数组大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

2 构造函数

构造函数 内容
public ArrayList(int initialCapacity) 设置初始容量,当为0时,创建一个初始容量为10的list,当超过0,则根据初始容量设置的大小创建ArrayList。
public ArrayList() 无参构造将会创建一个初始容量为10的list。
public ArrayList(Collection c) 设置Collection 作为参数构建ArrayList,它是将Collection 中的这个对象最终转换成ArrayList中存在的object数组 elementData来保存数据
public ArrayList(int initialCapacity) {    
  if (initialCapacity > 0) {
    	//超出0 ,根据设置的初始容量创建数组的大小
          this.elementData = new Object[initialCapacity];
      } else if (initialCapacity == 0) {
      // 为0 ,保存空实例的空数组
          this.elementData = EMPTY_ELEMENTDATA;
      } else {
          throw new IllegalArgumentException("Illegal Capacity: "+
                                             initialCapacity);
      }
}
public ArrayList() {
  //初始为空数组
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//创建了一个包含colleciton 元素的arraylist 要求返回的是这个collection的迭代
public ArrayList(Collection<? extends E> c) {
  //保存这个collection中的数据到elementData
    elementData = c.toArray();
  //如果这个数组不为空 ,如果这个数组不是object类型,就将其转化为object数组
    if ((size = elementData.length) != 0) {
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
      //为空,数组替换为保存空实例的空数组。
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

3常用方法

3.1 add
add方法 内容
public boolean add(E e) 直接添加元素
public void add(int index, E element) 将元素添加到指定下标。

add(E,e)方法运行时会调用ensureCapacityInternal方法判断数组大小是否足够,它会通过calculateCapacity方法去计算所需的最小数组大小。如果数组大小能够满足就继续运行,如果不足,数组就进行扩容。每次扩容的大小是原容量+原容量的1/2 。然后通过Arrays.copyof方法将原数组copy到新数组中。

add(index,element)是指定了要添加的元素值和对应的数组中的下标值来进行添加的。它不仅要判断数组大小是否足够,还要判断下标输入的是否正确,通过rangeCheckForAdd方法。它进行添加的方法不同于add(E,e),它是通过 System.arraycopy这个方法,从一个数组中指定的位置开始,copy到另一个数组中的指定位置,复制指定长度的元素。

不过吧,不论是Arrays.copyof还是System.arraycopy方法,它都涉及到了数组的copy,相对于其他,感觉速率要慢点。

 public boolean add(E e) {
       //将当前已经存储的大小+1之后,进行判断
        ensureCapacityInternal(size + 1);  // Increments modCount!!
      //将元素添加到数组对应下标位置
        elementData[size++] = e;
      //返回变量表明添加成功
        return true;
    }

在添加之前会判断空间是否足够

private void ensureCapacityInternal(int minCapacity) {
  //调用方法,其中minCapacity是添加这个元素所需要的最小大小,size+1。
 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

//计算空间的方法,传入了所需最小大小和数组
private static int calculateCapacity(Object[] elementData, int minCapacity) {
   //判断数组是否为空数组,如果是第一次添加元素,那么返回默认容量和
   //需要的最小容量中最大的。
  /*
    当数组第一次添加元素的时候,它是会被扩展为默认大小的即10,
    所以此时要对所需要的最小容量和默认容量进行比较。返回两者中最大的量以满足需要。*/
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {	
	return Math.max(DEFAULT_CAPACITY, minCapacity);
   } 
  //如果不是第一次添加元素,返回需要的最小空间。
	return minCapacity;
}

确保数组空间足够使用,如果不足够,则进行扩容。


private void ensureExplicitCapacity(int minCapacity) {
   // 修改结构的次数增加
   modCount++;
	// overflow-conscious code
   //如果此时需要的最小容量超出了数组的容量,就进行扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
3.1.1 扩容方法

扩容是通过对于数组的copy来实现的。

//进行扩容
private void grow(int minCapacity) {
    // overflow-conscious code
    //保存当前的大小
    int oldCapacity = elementData.length;
   //设置扩容的新容量大小为原来的容量+原来容量的1/2,也就是原来容量的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
  //如果此时新容量的大小满足了保存元素需要的最小容量,
  //那么新容量将被赋值为需要的最小容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
  //如果此时新容量超出了能够设置的最大数组大小,
  //那么新容量是能够使用的最小容量,一般是满足需要的。
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
   // copy原来的elementData到新数组中。
    elementData = Arrays.copyOf(elementData, newCapacity);
}

指定下标添加元素到ArrayList中

public void add(int index, E element) {
  //做一个健壮性的判断,判断索引值是否超出了已经记录的大小或者是否小于0
    rangeCheckForAdd(index);
	//同add(E element)相似,判断空间是否足够添加一个元素
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    /*
    	从elementData数组(第一个参数,要进行copy的数组)中的index开始,
    	copy到elementData(第三个参数,目标数组)中的index+1位置
    	copy的长度为size-index.
     */
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
  //为对应下标的元素进行赋值为element。
    elementData[index] = element;
  //记录数组已存元素的size增加。
    size++;
}
//对索引值进行健壮性的判断。
 private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
          //如果小于已存元素的大小或者下标小于0 就抛出异常。
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
 }
3.2 set
public E set(int index, E element) {
  ///对下标进行健壮性的判断
    rangeCheck(index);
	//获取数组中对应下标的元素,新元素替换旧元素,返回旧元素
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
3.3 get
public E get(int index) {
  //对下标进行健壮性的判断,返回对应下标中的元素
    rangeCheck(index);
    return elementData(index);
}
3.4 remove
删除的两种方法 内容
public E remove(int index) 根据索引删除
public boolean remove(Object o) 根据object进行删除
public E remove(int index) {
	// 对下标进行健壮性的判断
    rangeCheck(index);
    //修改结构的次数增加
    modCount++;
   // 获取数组中对应下标的旧元素
    E oldValue = elementData(index);
   // size - index -1  获取到数组中要删除元素的个数
    int numMoved = size - index - 1;
    // 如果这个删除元素的下标大于0 
    if (numMoved > 0)
    	/* 从elementData数组中的index+1开始,copy到elementData 中的index元素,
    	移动的元素的个数是numMoved*/
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
      //清空elementData数组中的这个下标对应元素
    elementData[--size] = null; // clear to let GC do its work
	//返回旧元素
    return oldValue;
}

通过对象删除元素

public boolean remove(Object o) {
  //如果元素为null
  if (o == null) {
    	//遍历(已添加元素的长度)。
        for (int index = 0; index < size; index++)
          //如果数组中对应下标中的元素值为null
            if (elementData[index] == null) {
              //就调用fastRemove进行删除,并返回true
                fastRemove(index);
                return true;
            }
    } else {
    //遍历
        for (int index = 0; index < size; index++)
            //如果数组中含有相等的对象,就调用fastRemove进行删除,并返回true
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

删除真正使用的方法

rivate void fastRemove(int index) {
  //修改结构的次数增加
    modCount++;
  //设置要删除的长度
    int numMoved = size - index - 1;
  //如果长度超出0 
    if (numMoved > 0)
      /*通过数组的copy来删除*/
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
  	//清空这个下标
    elementData[--size] = null; // clear to let GC do its work
}
####  4 常见问题
1 关于ArrayList的增删速度小于LinkedList,访问速度快于LinkedList

​ 从底层结构来说,ArrayList底层是数组,LinkedList底层是链表。数组是一块连续的内存空间,访问的时候,只需要一个接一个的进行查询就可以,而相对于链表,它在内存中的存放地址并不是固定的,查找的时候需要通过元素的前驱后继来指向上一个,下一个的地址来进行查询。所以ArrayList的增删速度要高于LindList。数组中元素的增删,会造成数组中元素的移动,而链表中依旧是只需要修改前驱和后继指向的元素,所以LinkedList的增删速度要高于ArrayList。

2. 关于设置EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA

​ 其中有两个变量EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA自己一开始看的时候,并不太明白为什么要设置两个功能差不多的变量,都是用于保存空实例的空数组。以下是看了网上的解释之后了解的。EMPTY_ELEMENTDATA是ArrayList的构造函数中指定了初始容量,不过当这个初始容量为0时,这个ArrayList的数组就被设置为了EMPTY_ELEMENTDATA,ArrayList的无参构造,返回的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

3.关于Arrays.copyof() 和System.arraycopy方法

在ArrayList中都是什么时候调用的这两个方法呢

​ 在ArrayList中增删时调用了System.arraycopy方法。(主要是我只看了添加和删除元素)。

​ 扩容时调用了Arrays.copyOf(elementData, newCapacity);

Arrays是集合中的一个工具类,与它相似的还有collectionss。它提供了很多关于不同类型数组的copy方法。这个虽然知道是数组的copy,但是还是不是很清晰,所以又找了源码来瞅瞅。可能还是不是明白,但是了解了解应该是不会错的。于是发现**在Arrays.copy方法的底层,竟然是调用了System.arraycopy方法。**不过具体就看不太懂了。不过找了博客看发现Arrays.copyof和System.arraycopy方法是四种数组copy方法之一(for,arrays.copyof, system.arraycopy,clone),其中system.arraycopy方法是四种中较快的一个。

/*
 * @param      src        源数组,也就是要被进行copy的数组
 * @param      srcPos     源数组中开始的下标值
 * @param      dest     the destination array. 目标数组
 * @param      destPos   目标数组中开始的下标值
 * @param      length     被copy的元素的长度
 */
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);
 /*
   复制指定的数组,用零来截断或填充(如果必要的话),这样拷贝就具有指定的长度。
   对于在原始数组和副本中都有效的所有索引,两个数组将包含相同的值。
   对于任何在副本中有效但不是原始的索引,拷贝将包含0。
   当且仅当指定长度大于原始数组时,这些索引将存在。 */
  /**
  @param original  要进行copy的数组
  @param newLength  需要copy的长度
 */
   public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
   }
  // copyOf(T[] original, int newLength) 在内部又调用了它
   /*
	复制指定的数组、截断或填充为空(如果必要),以便复制具有指定的长度。
	对于原始数组和副本中有效的所有索引,这两个数组将包含相同的值。
	对于副本中有效但不是原始索引,副本将包含null。
	如果且仅当指定的长度大于原始数组的长度时,才会存在此类索引。
	所得数组为newType类。
	
	@param  original  要进行copy的数组
	@param   newLength  要copy的数组的长度
	@param     newType   要进行copy的数组中元素的类型
	*/
    public static <T,U> T[] copyOf(U[] original, int newLength,
	   Class<? extends T[]> newType) {
	        // 判断是否是object类型,如果是创建一个newLength长度的object数组,
	        //如果不是Array创建一个newType类型newLength长度的数组
           T[] copy = ((Object)newType == (Object)Object[].class)
              ? (T[]) new Object[newLength]
              : (T[]) Array.newInstance(newType.getComponentType(), newLength);
   //从original的0下标开始,复制Max.min(original.length,newLength)长度的元素
   //到copy数组中(从0 下标开始)。
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
	 }

你可能感兴趣的:(JavaSE)