> 文档中心 > 【ArrayList】白话文源码分析

【ArrayList】白话文源码分析

【ArrayList】白话文源码分析

  • ArrayList类解析
    • 全局变/常量
    • 构造方法
      • 无参构造:
      • 有参构造(参数为int)
      • 有参构造(参数为Collection集合)
    • 其他方法
      • add()
        • 扩容机制:
      • remove()

打好基础,再说其他的,不要整天好高骛远。

希望我的分享能帮到一样迷茫但努力的同学。

ArrayList类解析

首先我想对一些基础不好同学说一句。ArrayList是一个,不是你们认为的就直接操作内存了。
既然ArrayList是一个类,那么类就有类的规则,有规则我们就能学习。我们来看看这个类的结构:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{1.全局变/常量    private static final long serialVersionUID = 8683452581122892189L;    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;===================================================================================2. 构造方法    public ArrayList(int initialCapacity) { if (initialCapacity > 0) {     this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) {     this.elementData = EMPTY_ELEMENTDATA; } else {     throw new IllegalArgumentException("Illegal Capacity: "+     initialCapacity); }    }    public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;    }    public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) {     if (elementData.getClass() != Object[].class)  elementData = Arrays.copyOf(elementData, size, Object[].class); } else {     this.elementData = EMPTY_ELEMENTDATA; }    }===================================================================================3. 其他方法。。。。。。。}

整个ArrayList类我们就分为三个模块来解释。

  1. 全局变/常量
  2. 构造方法
  3. 其他方法(继承自父类和接口的具体的集合操作方法,如.add()等)

全局变/常量

ArrayList操作和这些变/常量是息息相关的,我们必须要非常清楚得知道每一个变/常量的含义是什么。

定义 默认值 含义
serialVersionUID 8683452581122892189L 可以看到这是一个静态常量,这个常量主要是用于对象序列化时的唯一标识,这里我们不去了解它。
DEFAULT_CAPACITY 10 可以看到这是一个静态常量,默认为10,此字段用作表示ArrayList集合的初始化长度
EMPTY_ELEMENTDATA {} 可以看到这是一个静态常量,用于表示数组
DEFAULTCAPACITY_EMPTY_ELEMENTDATA {} 可以看到这是一个静态常量,用于表示空数组,但和EMPTY_ELEMENTDATA含义不同,DEFAULTCAPACITY_EMPTY_ELEMENTDATA用在初始化集合的时候。
elementData {} 这是一个变量,用于表示当前的集合对象增删改查都是操作的它。
size 0 这是一个变量,用于表示当前集合的长度。

构造方法

要了解上面的变/常量的含义,最好就是通过读代码一行一行得慢慢熟悉。
构造方法就是我们了解一个类的第一步。

我们发现ArrayList有三个构造方法,我们从易到难依次分析一下。

无参构造:

当我们平时写List myList = new ArrayList()的时候就会调用下面的这个无参构造方法创建一个ArrayList对象。
我们看看构造方法做了什么事情:

    public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;    }=================================================================================== 1.this.elementData。this指的是当前的ArrayList对象,elementData指的是当前对象的数组对象。    2.DEFAULTCAPACITY_EMPTY_ELEMENTDATA。指初始化时的空数组常量。    3.整个构造方法就是把一个空数组赋值给我们new的ArrayList对象。

因此,调用无参构造创建集合的时候,集合为空,长度为size=0;(int值默认为0)

有参构造(参数为int)

当我们平时写List myList = new ArrayList(5)的时候就会调用下面的这个有参构造方法创建一个ArrayList对象。
我们看看构造方法做了什么事情:

   public ArrayList(int initialCapacity) {   if (initialCapacity > 0) {     this.elementData = new Object[initialCapacity];  } else if (initialCapacity == 0) {     this.elementData = EMPTY_ELEMENTDATA; } else {     throw new IllegalArgumentException("Illegal Capacity: "+     initialCapacity); }    }===================================================================================    1.我们从参数名就知道,这个参数是用来指定集合创建时的集合初始长度的。2.如果参数>0,初始化并赋值给集合对象一个该参数长度的数组。3.如果参数=0,赋值给集合对象一个空数组。4.else,说明参数无效或非法,报错。

因此,我们可以利用这个构造方法在创建集合的时候就指定集合的长度

有参构造(参数为Collection集合)

Collection是List的父类,List是ArrayList的父类。
所以,当我们平时写List myList = new ArrayList(Collection子类集合)的时候就会调用下面的这个有参构造方法创建一个ArrayList对象。
比如我们现在要把一个Set集合赋值给ArrayList集合,就要调用这个构造方法。
我们看看构造方法做了什么事情:

    public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) {     if (elementData.getClass() != Object[].class)  elementData = Arrays.copyOf(elementData, size, Object[].class); } else {     this.elementData = EMPTY_ELEMENTDATA;===================================================================================1.把Collection集合参数.toArray()转成数组,并赋值给当前集合对象中的elementData变量。2.把elementData的长度赋值给变量size。3.如果size!=0,并且如果elementData的类模板对象不是Object[]的类模板对象4.就强转为Object对象并赋值给elementData (这里主要是防止数组类型不同做的预防)5.如果size为0,赋值给集合对象一个空数组。

看文章的时候记得反复回去看看全局变/常量那个表格。

其他方法

这里呢,就分析几个最常用的方法,主要是理解,不要死记硬背。

add()

当我们要给集合添加元素的时候会调用这个.add()方法。这也是要了解ArrayList的最重要的一个方法。
我们开始:

    public boolean add(E e) { ensureCapacityInternal(size + 1);   elementData[size++] = e; return true;    }===================================================================================    1.e就是我们要添加的元素2.方法进来第一行就去调用了一个ensureCapacityInternal方法,传入了当前size+1最为参数,作为期望最小容量去判断是否需要扩容。3. ensureCapacityInternal方法调用完成(成功)之后,把元素赋值到数组的最后一位。4. 返回true,添加完成。

很明显,重点就是在ensureCapacityInternal(方法里面了),我们点过去看看:

 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);    }===================================================================================1.第一行就是ensureCapacityInternal方法,我们看到这个方法先后调用了calculateCapacity和ensureExplicitCapacity方法。2.我们先看calculateCapacity方法,两个参数elementData:当前数组对象,长度为10。minCapacity:传过来的size+1,后面统称为最小容量。3.判断当前数组对象是否为空,如果为空就在默认容量(10)和最小容量选一个最大的返回。4.如果数组不为空,直接返回最小容量。5.我们拿到这个int值能干嘛呢。6.带着这个值来到ensureExplicitCapacity方法,第一步modCount++;我们不管它,主要是记录集合的操作数。如果最小需要空间比elementData的内存空间要大,则需要扩容,来到grow(minCapacity)方法。

因此,我们知道了,集合在添加第一个元素的时候才会初始化一个长度为10的集合。

接下来就是grow()方法了,顾名思义,grow,我们要开始扩容了。

扩容机制:

    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);    }===================================================================================1. 参数minCapacity是之前我们计算出来当前集合可以接受的最小长度。2. 先得到原数组的长度   oldCapacity 3. 再根据算法算出新长度  newCapacity (老长度的1.5倍)4.  再判断一下新数组的容量够不够,够了就直接使用这个长度创建新数组,    不够就将数组长度设置为需要的长度。5. 检查长度是否超出最大值。6. 调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间 并将elementData的数据复制到新的内存空间    

remove()

面试常问,为什么ArrayList对集合中元素的增删效率很低。我们来看看为什么:
下图是更具下标删除元素。假如我们现在有3个元素,我要删除下标是1的元素,底层会怎么做呢?

    public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0)     System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null;  // clear to let GC do its work return oldValue;    }===================================================================================1. 第一步,检查传入的下标是否合法。2. modCount++;操作数+13. 得到要删除的元素的值。4. numMoved,如果numMoved =0,说明被删除的元素是第一个或最后一个,不需要整体拷贝。numMoved为需要移动的元素个数。5. 通过判断,System.arraycopy方法是native修饰的,直接操作内存,整个数组下标移动,很慢。6. 因为删除了一个元素,size需要-1。并把数组最后一个元素置为null,等待GC删除。7. 返回被删除的值

其他的方法可以自己一步一步去看。