一、ArrayList 概述

  1、ArrayList 是 List 接口的典型实现类、主要实现类;

  2、ArrayList 是 List 接口的一个实现类,也是 Java 中最常用的容器实现类之一,可以把它理解为「可变数组」

  3、Java 中的数组初始化时需要指定长度,而且指定后不能改变。ArrayList 内部也是一个数组,它对数组的功能做了增强:主要是在容器内元素增加时可以动态扩容,这也是 ArrayList 的核心所在。

  4、学习了 List 接口的方法,ArrayList 的主要方法与 List 基本一致,因此这里重点分析其内部结构和扩容的原理。

  5、ArrayList 的继承结构如下(省略部分接口):

    

 

二、实现的接口

  源码:

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

     (1)List 接口:该接口定义了 List 接口里面常用的操作方法;

  (2)RandomAccess接口:接⼝中什么都没有定义,RandomAccess 接⼝不过是⼀个标识罢了。标识什么? 标识实现这个接⼝的类具有随机访问功能。RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。

  (3)Cloneable接口:实现了 Cloneable 接口,并重写了 clone()方法,表示该实现类是支持克隆的;

  (4)Serializable 接口:该接口中也什么都没有定义,该接口也只是一个标识,表明当前的类支持序列化,能够通过序列化去传输。

  

三、成员变量

  先来看一下 ArrayList 中的成员变量:

  JDK8的1.8.0_291 版本中:

 1 private static final long serialVersionUID = 8683452581122892189L;
 2 
 3 /**
 4  * Default initial capacity.
 5  */
 6 private static final int DEFAULT_CAPACITY = 10;   //默认容量 10
 7 
 8 /**
 9  * Shared empty array instance used for empty instances.
10  */
11 private static final Object[] EMPTY_ELEMENTDATA = {};  //空元素数据
12 
13 /**
14  * Shared empty array instance used for default sized empty instances. We
15  * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
16  * first element is added.
17  */
18 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};   //默认容量空元素数据
19 
20 /**
21  * The array buffer into which the elements of the ArrayList are stored.
22  * The capacity of the ArrayList is the length of this array buffer. Any
23  * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
24  * will be expanded to DEFAULT_CAPACITY when the first element is added.
25  */
26 transient Object[] elementData; // non-private to simplify nested class access  存储数据的数组
27 
28 /**
29  * The size of the ArrayList (the number of elements it contains).
30  *
31  * @serial
32  */
33 private int size;       //有效元素的个数
34 
35 /**
36  * The maximum size of array to allocate.
37  * Some VMs reserve some header words in an array.
38  * Attempts to allocate larger arrays may result in
39  * OutOfMemoryError: Requested array size exceeds VM limit
40  */
41 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

 

  JDK7 高阶版本中:

 1   private static final long serialVersionUID = 8683452581122892189L;
 2 
 3     /**
 4      * Default initial capacity.
 5      */
 6     private static final int DEFAULT_CAPACITY = 10;   //默认容量10
 7 
 8     /**
 9      * Shared empty array instance used for empty instances.
10      */
11     private static final Object[] EMPTY_ELEMENTDATA = {};
12 
13     /**
14      * The array buffer into which the elements of the ArrayList are stored.
15      * The capacity of the ArrayList is the length of this array buffer. Any
16      * empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to
17      * DEFAULT_CAPACITY when the first element is added.
18      */
19     private transient Object[] elementData;
20 
21     /**
22      * The size of the ArrayList (the number of elements it contains).
23      *
24      * @serial
25      */
26     private int size;
27 
28     /**
29      * The maximum size of array to allocate.
30      * Some VMs reserve some header words in an array.
31      * Attempts to allocate larger arrays may result in
32      * OutOfMemoryError: Requested array size exceeds VM limit
33      */
34     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

   JDK7 低级版本以及之前:

 1    private static final long serialVersionUID = 8683452581122892189L;
 2 
 3     /**
 4      * The array buffer into which the elements of the ArrayList are stored.
 5      * The capacity of the ArrayList is the length of this array buffer.
 6      */
 7     private transient Object[] elementData;
 8 
 9     /**
10      * The size of the ArrayList (the number of elements it contains).
11      *
12      * @serial
13      */
14     private int size;

 

四、构造器(JDK8[1.8.0_291])

  我们先从构造器进行分析。

  ArrayList 有三个构造器,下面进行学习:

  1、无参构造器

1     /**
2      * Constructs an empty list with an initial capacity of ten.
3      */
4     public ArrayList() {
5         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
6     }

 

    该构造器涉及两个变量:elementData 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

    可以看到上面对这两个成员变量的声明:

1   transient Object[] elementData; // non-private to simplify nested class access
2     /**
3      * Shared empty array instance used for default sized empty instances. We
4      * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
5      * first element is added.
6      */
7     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    可以看到 elementData 是一个 Object[] 类型的数组,该数组也是ArrayList 作为容器用于存储数据的地方。

    DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是一个 Object 类型的空数组。
    因此,该无参构造器的作用就是将 elementData 初始化为一个 Object 类型的空数组

 

  2、指定初始化容量的构造器

    该构造传入一个参数,即初始化内部数组容量的 initialCapacity,代码如下:

 1     public ArrayList(int initialCapacity) {
 2         if (initialCapacity > 0) {
 3             this.elementData = new Object[initialCapacity];
 4         } else if (initialCapacity == 0) {
 5             this.elementData = EMPTY_ELEMENTDATA;
 6         } else {
 7             throw new IllegalArgumentException("Illegal Capacity: "+
 8                                                initialCapacity);
 9         }
10     }

    当初始化容量为 0 时,elementData 被初始化为 EMPTY_ELEMENTDATA,该变量如下:

private static final Object[] EMPTY_ELEMENTDATA = {};

     该数组与 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 都是一个空的 Object 数组,二者名字不同是为了区分 ArrayList 初始化时是否指定了容量,后期进行扩容的时候有所不同。

 

    部分版本如下:

1     public ArrayList(int initialCapacity) {
2         super();
3         if (initialCapacity < 0)
4             throw new IllegalArgumentException("Illegal Capacity: "+
5                                                initialCapacity);
6         this.elementData = new Object[initialCapacity];
7     }

    该构造器根据传入的初始容量(initialCapacity)初始化用于存储元素的数组 elementData 变量。

    

  3、指定初始化集合的构造器

    该构造器传入一个集合 Collection,即使用 Collection 中的元素初始化 ArrayList 对象,代码如下:

 1   public ArrayList(Collection<? extends E> c) {
 2         Object[] a = c.toArray();
 3         if ((size = a.length) != 0) {
 4             if (c.getClass() == ArrayList.class) {
 5                 elementData = a;
 6             } else {
 7                 elementData = Arrays.copyOf(a, size, Object[].class);
 8             }
 9         } else {
10             // replace with empty array.
11             elementData = EMPTY_ELEMENTDATA;
12         }
13     }

 

    部分版本如下:

1     public ArrayList(Collection<? extends E> c) {
2         elementData = c.toArray();
3         size = elementData.length;
4         // c.toArray might (incorrectly) not return Object[] (see 6260652)
5         if (elementData.getClass() != Object[].class)
6             elementData = Arrays.copyOf(elementData, size, Object[].class);
7     }

     注意:这里若 Collection 为空时会抛出 NPE,因此初始化前有必要判空。

 

五、构造器(JDK7[1.7.0_7])

     1、无参构造

1     /**
2      * Constructs an empty list with an initial capacity of ten.
3      */
4     public ArrayList() {
5         this(10);
6     }

      可以看到,这里是创建了一个容量为10的ArrayList。

  2、指定初始化容量的构造器

1     public ArrayList(int initialCapacity) {
2         super();
3         if (initialCapacity < 0)
4             throw new IllegalArgumentException("Illegal Capacity: "+
5                                                initialCapacity);
6         this.elementData = new Object[initialCapacity];
7     }

 

  3、指定初始化集合的构造器

    源码:

1     public ArrayList(Collection<? extends E> c) {
2         elementData = c.toArray();
3         size = elementData.length;
4         // c.toArray might (incorrectly) not return Object[] (see 6260652)
5         if (elementData.getClass() != Object[].class)
6             elementData = Arrays.copyOf(elementData, size, Object[].class);
7     }

 

六、ArrayList 扩容机制分析

  1、先从 ArrayList 的构造函数说起

    (JDK8)ArrayList 有三种方式来初始化,构造方法源码如下:

 1 /**
 2      * 默认初始容量大小
 3      */
 4     private static final int DEFAULT_CAPACITY = 10;
 5 
 6 
 7     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 8 
 9     /**
10      *默认构造函数,使用初始容量10构造一个空列表(无参数构造)
11      */
12     public ArrayList() {
13         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
14     }
15 
16     /**
17      * 带初始容量参数的构造函数。(用户自己指定容量)
18      */
19     public ArrayList(int initialCapacity) {
20         if (initialCapacity > 0) {//初始容量大于0
21             //创建initialCapacity大小的数组
22             this.elementData = new Object[initialCapacity];
23         } else if (initialCapacity == 0) {//初始容量等于0
24             //创建空数组
25             this.elementData = EMPTY_ELEMENTDATA;
26         } else {//初始容量小于0,抛出异常
27             throw new IllegalArgumentException("Illegal Capacity: "+
28                                                initialCapacity);
29         }
30     }
31 
32 
33    /**
34     *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
35     *如果指定的集合为null,throws NullPointerException。
36     */
37      public ArrayList(Collection<? extends E> c) {
38         elementData = c.toArray();
39         if ((size = elementData.length) != 0) {
40             // c.toArray might (incorrectly) not return Object[] (see 6260652)
41             if (elementData.getClass() != Object[].class)
42                 elementData = Arrays.copyOf(elementData, size, Object[].class);
43         } else {
44             // replace with empty array.
45             this.elementData = EMPTY_ELEMENTDATA;
46         }
47     }

 

    可以发现以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。 下面在我们分析 ArrayList 扩容时会讲到这一点内容!

    注意JDK7 new无参构造的ArrayList对象时,直接创建了长度是10的Object[]数组elementData 。jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式。JDK8的内存优化也值得我们在平时开发中学习。

  2、一步一步分析 ArrayList 扩容机制

    这里以无参构造函数创建的 ArrayList 为例分析

    (1)先来看 add 方法(addAll()方法类似)

 1     /**
 2      * 将指定的元素追加到此列表的末尾。
 3      */
 4     public boolean add(E e) {
 5        //添加元素之前,先调用ensureCapacityInternal方法
 6         ensureCapacityInternal(size + 1);  // Increments modCount!!    确保内部容量
 7         //这里看到ArrayList添加元素的实质就相当于为数组赋值
 8         elementData[size++] = e;
 9         return true;
10     }

        可以看到,在 add() 方法执行时,会首先执行  ensureCapacityInternal() 方法:(确保内部容量够用)

        注意 :JDK11 移除了 ensureCapacityInternal()ensureExplicitCapacity() 方法

    (2)再来看看 ensureCapacityInternal() 方法

      (JDK7)可以看到 add 方法 首先调用了ensureCapacityInternal(size + 1)

1    //得到最小扩容量
2     private void ensureCapacityInternal(int minCapacity) {
3         if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
4               // 获取默认的容量和传入参数的较大值
5             minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
6         }
7 
8         ensureExplicitCapacity(minCapacity);
9     }

         Tip:此处和后续 JDK8 代码格式化略有不同,核心代码基本一样。

      (JDK8)可以看到 add 方法中首先调用了ensureCapacityInternal(size + 1)

1     private void ensureCapacityInternal(int minCapacity) {
2         ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
3     }
4     private static int calculateCapacity(Object[] elementData, int minCapacity) {
5         if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
6             return Math.max(DEFAULT_CAPACITY, minCapacity);
7         }
8         return minCapacity;
9     }

       当 要 add 进第 1 个元素时,minCapacity 为 1,在 Math.max()方法比较后,minCapacity 为 10。

       这里只会在使用无参构造器初始化,并且第一次使用 add 方法时执行(将容量初始化为 10)

    (3)ensureExplicitCapacity()方法(确保明确的容量)

       如果调用 ensureCapacityInternal() 方法就一定会进入(执行)这个方法,下面我们来研究一下这个方法的源码!

1    //判断是否需要扩容  minCapacity 为所需最小容量
2     private void ensureExplicitCapacity(int minCapacity) {
3         modCount++;
4 
5         // overflow-conscious code
6         if (minCapacity - elementData.length > 0)
7             //调用grow方法进行扩容,调用此方法代表已经开始扩容了
8             grow(minCapacity);
9     }

 

    我们来仔细分析一下:

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

      • 当 add 第 2 个元素时,minCapacity 为 2,此时 e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时,minCapacity - elementData.length > 0 不成立,所以不会进入 (执行)grow(minCapacity) 方法。

      • 添加第 3、4···到第 10 个元素时,依然不会执行 grow 方法,数组容量都为 10。

      直到添加第 11 个元素,minCapacity(为 11)比 elementData.length(为 10)要大。进入 grow 方法进行扩容。

    (4)grow()方法

 1   /**
 2      * 要分配的最大数组大小
 3      */
 4     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
 5 
 6     /**
 7      * ArrayList扩容的核心方法。
 8      */
 9     private void grow(int minCapacity) {
10         // oldCapacity为旧容量,newCapacity为新容量
11         int oldCapacity = elementData.length;
12         //将oldCapacity 右移一位,其效果相当于oldCapacity /2,
13         //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
14         int newCapacity = oldCapacity + (oldCapacity >> 1);
15         //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
16         if (newCapacity - minCapacity < 0)
17             newCapacity = minCapacity;
18        // 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,
19        //如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。
20         if (newCapacity - MAX_ARRAY_SIZE > 0)
21             newCapacity = hugeCapacity(minCapacity);
22         // minCapacity is usually close to size, so this is a win:
23         elementData = Arrays.copyOf(elementData, newCapacity);
24     }

 

  int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右(oldCapacity 为偶数就是 1.5 倍,否则是 1.5 倍左右)! 奇偶不同,比如 :10+10/2 = 15, 33+33/2=49。如果是奇数的话会丢掉小数.

">>"(移位运算符):>>1 右移一位相当于除 2,右移 n 位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了 1 位所以相当于 oldCapacity /2。对于大数据的 2 进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源

  我们再来通过例子探究一下grow() 方法 :

    •  当 add 第 1 个元素时,oldCapacity 为 0,经比较后第一个 if 判断成立,newCapacity = minCapacity(为 10)。但是第二个 if 判断不会成立,即 newCapacity 不比 MAX_ARRAY_SIZE 大,则不会进入 hugeCapacity 方法。数组容量为 10,add 方法中 return true,size 增为 1。

    •  当 add 第 11 个元素进入 grow 方法时,newCapacity 为 15,比 minCapacity(为 11)大,第一个 if 判断不成立。新容量没有大于数组最大 size,不会进入 hugeCapacity 方法。数组容量扩为 15,add 方法中 return true,size 增为 11。

    •  以此类推······

    由此可以看出,新容量为原容量的 1.5 倍;

    若扩容为 1.5 倍后,仍未达到所需容量,则直接使用所需要的容量。
    可以发现是调用了 Arrays 类的 copyOf() 方法,进入 copyOf() 方法,发现最终调用了 System.arraycopy() 方法:

  这里补充一点比较重要,但是容易被忽视掉的知识点:

    •  java 中的 length属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.

    •  java 中的 length() 方法是针对字符串说的,如果想看这个字符串的长度则用到 length() 这个方法.

    •  java 中的 size() 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!

 

    (5)hugeCapacity()方法(巨大的容量)

从上面 grow() 方法源码我们知道: 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) hugeCapacity() 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果 minCapacity 大于最大容量,则新容量则为Integer.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8

 1   private static int hugeCapacity(int minCapacity) {
 2         if (minCapacity < 0) // overflow
 3             throw new OutOfMemoryError();
 4         //对minCapacity和MAX_ARRAY_SIZE进行比较
 5         //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
 6         //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
 7         //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
 8         return (minCapacity > MAX_ARRAY_SIZE) ?
 9             Integer.MAX_VALUE :
10             MAX_ARRAY_SIZE;
11     }

 

  3、System.arraycopy()和 Arrays.copyOf()方法

    阅读源码的话,我们就会发现 ArrayList 中大量调用了这两个方法。比如:我们上面讲的扩容操作以及add(int index, E element)toArray() 等方法中都用到了该方法!

    (1)System.arraycopy()方法

 1  /**
 2      * 在此列表中的指定位置插入指定的元素。
 3      *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大;
 4      *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
 5      */
 6     public void add(int index, E element) {
 7         rangeCheckForAdd(index);
 8 
 9         ensureCapacityInternal(size + 1);  // Increments modCount!!
10         //arraycopy()方法实现数组自己复制自己
11         //elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量;
12         System.arraycopy(elementData, index, elementData, index + 1, size - index);
13         elementData[index] = element;
14         size++;
15     }

       System.arraycopy() 源码:

1     public static native void arraycopy(Object src,  int  srcPos,
2                                         Object dest, int destPos,
3                                         int length);

 

      我们写一个简单的方法测试以下:

 1 public class ArraycopyTest {
 2 
 3     public static void main(String[] args) {
 4         // TODO Auto-generated method stub
 5         int[] a = new int[10];
 6         a[0] = 0;
 7         a[1] = 1;
 8         a[2] = 2;
 9         a[3] = 3;
10         System.arraycopy(a, 2, a, 3, 3);
11         a[2]=99;
12         for (int i = 0; i < a.length; i++) {
13             System.out.print(a[i] + " ");
14         }
15     }
16 
17 }

  结果:

1 0 1 99 2 3 0 0 0 0 0

 

    (2)Arrays.copyOf()方法

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

       Arrays.copyOf()源码:

 1     public static <T> T[] copyOf(T[] original, int newLength) {
 2         return (T[]) copyOf(original, newLength, original.getClass());
 3     }
 4     public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
 5         @SuppressWarnings("unchecked")
 6         T[] copy = ((Object)newType == (Object)Object[].class)
 7             ? (T[]) new Object[newLength]
 8             : (T[]) Array.newInstance(newType.getComponentType(), newLength);
 9         System.arraycopy(original, 0, copy, 0,
10                          Math.min(original.length, newLength));
11         return copy;
12     }

 

  个人觉得使用 Arrays.copyOf()方法主要是为了给原有数组扩容,测试代码如下:

 1 public class ArrayscopyOfTest {
 2 
 3     public static void main(String[] args) {
 4         int[] a = new int[3];
 5         a[0] = 0;
 6         a[1] = 1;
 7         a[2] = 2;
 8         int[] b = Arrays.copyOf(a, 10);
 9         System.out.println("b.length"+b.length);
10     }
11 }

    结果:

1 10

 

    (3)两者联系和区别

      联系:

        看两者源代码可以发现 copyOf()内部实际调用了 System.arraycopy() 方法

      区别:

        arraycopy() 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 copyOf() 是系统自动在内部新建一个数组,并返回该数组。

  4、ensureCapacity方法

      ArrayList 源码中有一个 ensureCapacity 方法不知道大家注意到没有,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的,那么这个方法有什么作用呢?

 1  /**
 2     如有必要,增加此 ArrayList 实例的容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。
 3      *
 4      * @param   minCapacity   所需的最小容量
 5      */
 6     public void ensureCapacity(int minCapacity) {
 7         int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
 8             // any size if not default element table
 9             ? 0
10             // larger than default for default empty table. It's already
11             // supposed to be at default size.
12             : DEFAULT_CAPACITY;
13 
14         if (minCapacity > minExpand) {
15             ensureExplicitCapacity(minCapacity);
16         }
17     }

 

      最好在 add 大量元素之前用 ensureCapacity 方法,以减少增量重新分配的次数

      我们通过下面的代码实际测试以下这个方法的效果:

 1 public class EnsureCapacityTest {
 2     public static void main(String[] args) {
 3         ArrayList<Object> list = new ArrayList<Object>();
 4         final int N = 10000000;
 5         long startTime = System.currentTimeMillis();
 6         for (int i = 0; i < N; i++) {
 7             list.add(i);
 8         }
 9         long endTime = System.currentTimeMillis();
10         System.out.println("使用ensureCapacity方法前:"+(endTime - startTime));
11 
12     }
13 }

    运行结果:

1 使用ensureCapacity方法前:2158

 

 1 public class EnsureCapacityTest {
 2     public static void main(String[] args) {
 3         ArrayList<Object> list = new ArrayList<Object>();
 4         final int N = 10000000;
 5         list = new ArrayList<Object>();
 6         long startTime1 = System.currentTimeMillis();
 7         list.ensureCapacity(N);
 8         for (int i = 0; i < N; i++) {
 9             list.add(i);
10         }
11         long endTime1 = System.currentTimeMillis();
12         System.out.println("使用ensureCapacity方法后:"+(endTime1 - startTime1));
13     }
14 }

      运行结果:

1 使用ensureCapacity方法前:1773

      通过运行结果,我们可以看出向 ArrayList 添加大量元素之前最好先使用ensureCapacity 方法,以减少增量重新分配的次数。

 

  5、扩容小结

    (1) 若未指定初始化容量
      当第一次执行 add() 方法时,将数组长度默认初始化为 10,之后再添加元素时不扩容,直至容量等于 10,再添加第 11 个元素时,将容量扩容为 15 (10 + 10 >> 1),以此类推。
 
    (2) 若指定了初始化容量 initialCapacity
      当数组容量到达 initialCapacity 之前,不进行扩容,当容量等于 initialCapacity 时若再添加元素,则执行扩容,扩容操作同上。
 
    (3)  新容量大小
      默认扩容后数组的容量为原数组容量的 1.5 倍;若仍未达到所需大小(使用 addAll() 方法时可能出现),则扩容为所需的容量。
    (4)jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
    (5)建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)

 

七、其他常用方法

  1、add(int index, E element)

    源码分析:

 1     public void add(int index, E element) {
 2         rangeCheckForAdd(index);  //范围检查
 3         
 4         //查看是否需要进行扩容,同扩容原理
 5         ensureCapacityInternal(size + 1);  // Increments modCount!!
 6         
 7         //把 elementData 数组中从 index 开始移动到 index+1的位置,移动了 size - index 个元素,即空出来 index 改位置
 8         System.arraycopy(elementData, index, elementData, index + 1,
 9                          size - index);
10         elementData[index] = element;
11         size++;
12     }
13     
14     //该方法是为了 add() 和 addAll() 方法所进行的索引校验
15     private void rangeCheckForAdd(int index) {
16         if (index > size || index < 0)
17             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
18     }

 

  2、addAll(Collection<? extends E> c) 

    源码分析:

 1     public boolean addAll(Collection<? extends E> c) {
 2         Object[] a = c.toArray();   // 把集合 c 转成 数组
 3         int numNew = a.length;      // 获取数组长度
 4         //是否需要扩容,确保内部容量
 5         ensureCapacityInternal(size + numNew);  // Increments modCount
 6         //将数组 a 从开始位置全部复制到 elementData 的 后面,复制了 numNew 个元素
 7         System.arraycopy(a, 0, elementData, size, numNew);
 8         size += numNew;             //加上添加集合的长度
 9         return numNew != 0;
10     }

 

  3、addAll(int index, Collection<? extends E> c) 

     源码分析:

 1     public boolean addAll(int index, Collection<? extends E> c) {
 2         rangeCheckForAdd(index);       //范围检查
 3 
 4         Object[] a = c.toArray();      //将 集合 c 转成 数组
 5         int numNew = a.length;         //获取数组的长度
 6         
 7         //是否需要扩容,确保内部容量
 8         ensureCapacityInternal(size + numNew);  // Increments modCount
 9 
10         int numMoved = size - index;    //计算需要移动多少个元素
11         if (numMoved > 0)
12             //把 elementData 数组从 index开始移动,移动到 index+numNew的位置,移动了 numMoved个元素,即为 数组a 空出地方
13             System.arraycopy(elementData, index, elementData, index + numNew,
14                              numMoved);
15         //再把数组 a 的全部元素复制到 elementData中,以index为起始位置,复制了数组a的长度numNew 个
16         System.arraycopy(a, 0, elementData, index, numNew);
17         size += numNew;
18         return numNew != 0;
19     }
20     private void rangeCheckForAdd(int index) {
21         if (index > size || index < 0)
22             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
23     }

 

  4、get(int index)

    源码分析:

 1     public E get(int index) {
 2         rangeCheck(index);  //范围检查,index 是否合理
 3 
 4         return elementData(index); //获取元素
 5     }
 6     
 7     private void rangeCheck(int index) {
 8         if (index >= size)
 9             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
10     }
11     
12     E elementData(int index) {
13         return (E) elementData[index];
14     }

 

  5、set(int index, E element)

    源码分析:

 1     public E set(int index, E element) {
 2         rangeCheck(index);               //范围检查
 3 
 4         E oldValue = elementData(index); //获取当前索引的值
 5         elementData[index] = element;    //给索引设置新的值
 6         return oldValue;                 //返回旧值
 7     }
 8     private void rangeCheck(int index) {
 9         if (index >= size)
10             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
11     }
12     E elementData(int index) {
13         return (E) elementData[index];
14     }

 

  6、remove(int index)

    源码分析:

 1     public E remove(int index) {
 2         rangeCheck(index);    //范围检查
 3 
 4         modCount++;
 5         E oldValue = elementData(index);  //获取index位置的值
 6 
 7         int numMoved = size - index - 1;  //需要移动的元素个数
 8         if (numMoved > 0)
 9             //把 elementData 数组中从 index+1 位置移动到 index 位置,移动 numMoved 个元素
10             System.arraycopy(elementData, index+1, elementData, index,
11                              numMoved);
12         elementData[--size] = null; // clear to let GC do its work   让GC快速执行
13 
14         return oldValue;                 //返回旧值
15     }
16     private void rangeCheck(int index) {
17         if (index >= size)
18             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
19     }

 

  7、remove(Object o)

    源码分析:

 1     public boolean remove(Object o) {
 2         if (o == null) {
 3             for (int index = 0; index < size; index++)
 4                 if (elementData[index] == null) {
 5                     fastRemove(index);
 6                     return true;
 7                 }
 8         } else {
 9             for (int index = 0; index < size; index++)
10                 if (o.equals(elementData[index])) {
11                     fastRemove(index);
12                     return true;
13                 }
14         }
15         return false;
16     }
17     //快速移动
18     private void fastRemove(int index) {
19         modCount++;
20         int numMoved = size - index - 1;  //计算所需要移动的元素个数
21         if (numMoved > 0)
22             System.arraycopy(elementData, index+1, elementData, index,
23                              numMoved);
24         elementData[--size] = null; // clear to let GC do its work
25     }
26     

    可以看到如果根据元素进行移除,都是先找到元素所对应的位置,然后再进行删除。

 

  8、removeAll(Collection<?> c)

    源码分析:

 1     public boolean removeAll(Collection<?> c) {
 2         Objects.requireNonNull(c);   //要求非空判断
 3         return batchRemove(c, false);
 4     }
 5     private boolean batchRemove(Collection<?> c, boolean complement) {
 6         final Object[] elementData = this.elementData;
 7         int r = 0, w = 0;
 8         boolean modified = false;
 9         try {
10             //遍历整个集合 elementData
11             for (; r < size; r++)
12                 //如果 集合 c 中不包含 elementData[r],则把该元素保存下来
13                 if (c.contains(elementData[r]) == complement)
14                     elementData[w++] = elementData[r];
15         } finally {
16             // Preserve behavioral compatibility with AbstractCollection,
17             // even if c.contains() throws.
18             //无论 contains() 是否抛出异常,只要 r!=size,就把 r到size的元素移动到elementData的w位置之后,移动 size-r个元素
19             if (r != size) {
20                 System.arraycopy(elementData, r,
21                                  elementData, w,
22                                  size - r);
23                 w += size - r;   //elementData 中有效元素个数:w + size - r
24             }
25             //只要 w != size,就把 elementData 中 w 到 size 位置都设为 null,方便 GC 快速工作
26             if (w != size) {
27                 // clear to let GC do its work
28                 for (int i = w; i < size; i++)
29                     elementData[i] = null;
30                 modCount += size - w;   //修改的数量:加上 size - w 次
31                 size = w;               //elementData 中有效元素个数为 w
32                 modified = true;
33             }
34         }
35         return modified;
36     }

removeAll和remove方法思想也是类似的,但是这里有个细节我认为作者处理的非常妙,有必要拿出来品味一下。那么妙在哪里呢?原来这里有两个方法removeAll和retainAll他们正好是互斥的两个操作,但是底层都调用了同一个方法来实现,请看! 

 1 // 删除包含集合C的元素
 2 publicboolean removeAll(Collection<?> c){
 3     Objects.requireNonNull(c);
 4     return batchRemove(c,false);
 5 }
 6 
 7 // 除了包含集合C的元素外,一律被删除。也就是说,最后只剩下c中的元素。
 8 publicboolean retainAll(Collection<?> c){
 9     Objects.requireNonNull(c);
10     return batchRemove(c,true);
11 }
12 
13 private boolean batchRemove(Collection<?> c,boolean complement){
14     final Object[] elementData =this.elementData;
15     int r =0, w =0;
16     boolean modified =false;
17     try{
18         for(; r < size; r++)
19             // 在这里有两点值得我们学习
20             // 第一,作者巧妙的提取了逻辑上的最大公约数,仅通过一行逻辑判断就实现了两个互斥的效果。
21             // 第二,作者的所用操作都集中于elementData一个数组上,避免了资源的浪费。
22             if(c.contains(elementData[r])== complement)
23                 elementData[w++]= elementData[r];
24     }finally{
25         // 理论上r==size 只有当出现异常情况的时候,才会出现r!=size,一旦出现了异常,
26         // 那么务必要将之前被修改过的数组再还原回来。
27         if(r != size){
28             System.arraycopy(elementData, r,
29                              elementData, w,
30                              size - r);
31             w += size - r;
32         }
33         
34         if(w != size){
35             // 被删除部分数组后,剩余的所有元素被移到了0-w之间的位置,w位置以后的元素都被置空回收。
36             for(int i = w; i < size; i++)
37                 elementData[i]=null;
38             modCount += size - w;
39             size = w;
40             modified =true;
41         }
42     }
43     return modified;
44 }

 

  9、retainAll(Collection<?> c)

    源码分析:

 1     //计算集合的交集,对集合 c 不会有影响
 2     public boolean retainAll(Collection<?> c) {    
 3         Objects.requireNonNull(c);       //要求非空判断
 4         return batchRemove(c, true);
 5     }
 6     private boolean batchRemove(Collection<?> c, boolean complement) {
 7         final Object[] elementData = this.elementData;
 8         int r = 0, w = 0;
 9         boolean modified = false;
10         try {
11             for (; r < size; r++)
12                 if (c.contains(elementData[r]) == complement)
13                     elementData[w++] = elementData[r];
14         } finally {
15             // Preserve behavioral compatibility with AbstractCollection,
16             // even if c.contains() throws.
17             if (r != size) {
18                 System.arraycopy(elementData, r,
19                                  elementData, w,
20                                  size - r);
21                 w += size - r;
22             }
23             if (w != size) {
24                 // clear to let GC do its work
25                 for (int i = w; i < size; i++)
26                     elementData[i] = null;
27                 modCount += size - w;
28                 size = w;
29                 modified = true;
30             }
31         }
32         return modified;
33     }

 

  10、indexOf(Object o)

    源码分析:

 1     public int indexOf(Object o) {
 2         if (o == null) {
 3             for (int i = 0; i < size; i++)
 4                 if (elementData[i]==null)
 5                     return i;
 6         } else {
 7             for (int i = 0; i < size; i++)
 8                 if (o.equals(elementData[i]))
 9                     return i;
10         }
11         return -1;
12     }

 

    indexOf() 就是从头开始遍历整个数组,查看是否能够找到与之匹配的元素,找到返回索引下标,找不到返回 -1。

  11、lastIndexOf(Object o)

    源码分析:

 1     public int lastIndexOf(Object o) {
 2         if (o == null) {
 3             for (int i = size-1; i >= 0; i--)
 4                 if (elementData[i]==null)
 5                     return i;
 6         } else {
 7             for (int i = size-1; i >= 0; i--)
 8                 if (o.equals(elementData[i]))
 9                     return i;
10         }
11         return -1;
12     }

    lastIndexOf() 原理同 indexOf(),只是此时从数组尾部开始进行遍历。

 

  12、subList(int fromIndex, int toIndex):返回的是原集合的一个子集合(视图)

    源码分析:

 1     public List<E> subList(int fromIndex, int toIndex) {
 2         subListRangeCheck(fromIndex, toIndex, size);    //范围检查
 3         return new SubList(this, 0, fromIndex, toIndex); //包括 fromIndex 不包含 toIndex
 4     }
 5     
 6     static void subListRangeCheck(int fromIndex, int toIndex, int size) {
 7         if (fromIndex < 0)
 8             throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
 9         if (toIndex > size)
10             throw new IndexOutOfBoundsException("toIndex = " + toIndex);
11         if (fromIndex > toIndex)
12             throw new IllegalArgumentException("fromIndex(" + fromIndex +
13                                                ") > toIndex(" + toIndex + ")");
14     }
15     
16     //私有内部类,List接口 AbstractList接口的实现类,但不等于 ArrayList
17     private class SubList extends AbstractList<E> implements RandomAccess {
18         private final AbstractList<E> parent;
19         private final int parentOffset;
20         private final int offset;
21         int size;
22     }
23     //构造器
24     SubList(AbstractList<E> parent,
25                 int offset, int fromIndex, int toIndex) {
26             this.parent = parent;
27             this.parentOffset = fromIndex;
28             this.offset = offset + fromIndex;
29             this.size = toIndex - fromIndex;     //从 fromIndex 开始计算,移动size个
30             this.modCount = ArrayList.this.modCount;
31     }

 这里指的子集,就是指定list的起始位置和结束位置,获取这段范围内的集合元素。那么这有什么作用呢?当单独获取了这段子集以后,就可以独立的对待他,他的起始元素将从0开始。那么这是怎么实现的呢?原来他是通过维护一个SubList内部类,每次读取元素的时候,配合一个offset偏移量,精确的找到elementData数组中对应位置的元素了。

 

  13、replaceAll(UnaryOperator<E> operator)

    源码分析:

 1     public void replaceAll(UnaryOperator<E> operator) {
 2         Objects.requireNonNull(operator);  //要求非空判断
 3         final int expectedModCount = modCount;
 4         final int size = this.size;
 5         for (int i=0; modCount == expectedModCount && i < size; i++) {
 6             elementData[i] = operator.apply((E) elementData[i]);
 7         }
 8         if (modCount != expectedModCount) {
 9             throw new ConcurrentModificationException();
10         }
11         modCount++;
12     }

 

  14、序列化方法:writeObject(java.io.ObjectOutputStream s)

    源码分析:

 1     private void writeObject(java.io.ObjectOutputStream s)
 2         throws java.io.IOException{
 3         // Write out element count, and any hidden stuff
 4         int expectedModCount = modCount;
 5         s.defaultWriteObject();
 6 
 7         // Write out size as capacity for behavioural compatibility with clone()
 8         s.writeInt(size);
 9 
10         // Write out all elements in the proper order.
11         for (int i=0; i<size; i++) {
12             s.writeObject(elementData[i]);
13         }
14 
15         if (modCount != expectedModCount) {
16             throw new ConcurrentModificationException();
17         }
18     }

 

  15、反序列化方法:readObject(java.io.ObjectInputStream s)

    源码分析:

 1   private void readObject(java.io.ObjectInputStream s)
 2         throws java.io.IOException, ClassNotFoundException {
 3         elementData = EMPTY_ELEMENTDATA;
 4 
 5         // Read in size, and any hidden stuff
 6         s.defaultReadObject();
 7 
 8         // Read in capacity
 9         s.readInt(); // ignored
10 
11         if (size > 0) {
12             // be like clone(), allocate array based upon size not capacity
13             ensureCapacityInternal(size);
14 
15             Object[] a = elementData;
16             // Read in all elements in the proper order.
17             for (int i=0; i<size; i++) {
18                 a[i] = s.readObject();
19             }
20         }
21     }

 

  16、迭代器

    在java集合类中,所有的集合都实现了Iterator接口,而List接口同时实现了ListIterator接口,这就决定了ArrayList他同时拥有两种迭代遍历的基因—Itr和ListItr。

    (1)Itr

      Itr实现的是Iterator接口,拥有对元素向后遍历的能力

 1 int cursor;// 指向下一个返回的元素
 2 int lastRet =-1;// 指向在遍历过程中,最后返回的那个元素。 如果没有为-1。
 3 
 4 public boolean hasNext() {
 5     return cursor != size;
 6 }
 7 
 8 public E next(){
 9     checkForComodification();
10     int i = cursor;
11     if(i >= size)
12         thrownewNoSuchElementException();
13     Object[] elementData =ArrayList.this.elementData;
14     if(i >= elementData.length)
15         throw new ConcurrentModificationException();
16     // 指向下一个元素
17     cursor = i +1;
18     // 返回当前元素,并把lastRet指向当前这个元素
19     return(E) elementData[lastRet = i];
20 }
21 
22 
23 // 此处有坑,调用此方法前,必须调用next方法,从lastRet可以看出,如果当前没有调用next,那么lastRet==-1
24 publicvoid remove(){
25     if(lastRet <0)
26         thrownewIllegalStateException();
27     checkForComodification();
28     try{
29         ArrayList.this.remove(lastRet);
30         cursor = lastRet;
31         lastRet =-1;
32         expectedModCount = modCount;
33     }catch(IndexOutOfBoundsException ex){
34         throw new ConcurrentModificationException();
35     }
36 }

 

    (2)ListItr

      ListItr不但继承了Itr类,也实现了ListIterator接口,因此他拥有双向遍历的能力。这里着重介绍一下向前遍历的原理。

 1 public boolean hasPrevious(){
 2     return cursor !=0;
 3 }
 4 
 5 public int previousIndex(){
 6     // 通过cursor-1,将指针向前移位。
 7     return cursor -1;
 8 }
 9 public E previous(){
10     checkForComodification();
11     int i = cursor -1;
12     if(i <0)
13         throw new NoSuchElementException();
14     Object[] elementData =ArrayList.this.elementData;
15     if(i >= elementData.length)
16         throw new ConcurrentModificationException();
17     cursor = i;
18     return(E) elementData[lastRet = i];
19 }

 

    ListItr同时增加了set和add两个方法

 1 // 替换当前遍历到的元素
 2 publicvoidset(E e){
 3     if(lastRet <0)
 4         throw new IllegalStateException();
 5     checkForComodification();
 6     try{
 7         ArrayList.this.set(lastRet, e);
 8     }catch(IndexOutOfBoundsException ex){
 9         throw new ConcurrentModificationException();
10     }
11 }
12 
13 // 添加一个元素到当前遍历到的位置
14 publicvoid add(E e){
15     checkForComodification();
16     try{
17         int i = cursor;
18         ArrayList.this.add(i, e);
19         cursor = i +1;
20         lastRet =-1;
21         expectedModCount = modCount;
22     }catch(IndexOutOfBoundsException ex){
23         throw new ConcurrentModificationException();
24     }
25 }

 

八、ArrayList 核心源码解读

  核心源码解读:

  1 package java.util;
  2 
  3 import java.util.function.Consumer;
  4 import java.util.function.Predicate;
  5 import java.util.function.UnaryOperator;
  6 
  7 
  8 public class ArrayList<E> extends AbstractList<E>
  9         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
 10 {
 11     private static final long serialVersionUID = 8683452581122892189L;
 12 
 13     /**
 14      * 默认初始容量大小
 15      */
 16     private static final int DEFAULT_CAPACITY = 10;
 17 
 18     /**
 19      * 空数组(用于空实例)。
 20      */
 21     private static final Object[] EMPTY_ELEMENTDATA = {};
 22 
 23      //用于默认大小空实例的共享空数组实例。
 24       //我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
 25     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 26 
 27     /**
 28      * 保存ArrayList数据的数组
 29      */
 30     transient Object[] elementData; // non-private to simplify nested class access
 31 
 32     /**
 33      * ArrayList 所包含的元素个数
 34      */
 35     private int size;
 36 
 37     /**
 38      * 带初始容量参数的构造函数(用户可以在创建ArrayList对象时自己指定集合的初始大小)
 39      */
 40     public ArrayList(int initialCapacity) {
 41         if (initialCapacity > 0) {
 42             //如果传入的参数大于0,创建initialCapacity大小的数组
 43             this.elementData = new Object[initialCapacity];
 44         } else if (initialCapacity == 0) {
 45             //如果传入的参数等于0,创建空数组
 46             this.elementData = EMPTY_ELEMENTDATA;
 47         } else {
 48             //其他情况,抛出异常
 49             throw new IllegalArgumentException("Illegal Capacity: "+
 50                                                initialCapacity);
 51         }
 52     }
 53 
 54     /**
 55      *默认无参构造函数
 56      *DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10
 57      */
 58     public ArrayList() {
 59         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
 60     }
 61 
 62     /**
 63      * 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
 64      */
 65     public ArrayList(Collection<? extends E> c) {
 66         //将指定集合转换为数组
 67         elementData = c.toArray();
 68         //如果elementData数组的长度不为0
 69         if ((size = elementData.length) != 0) {
 70             // 如果elementData不是Object类型数据(c.toArray可能返回的不是Object类型的数组所以加上下面的语句用于判断)
 71             if (elementData.getClass() != Object[].class)
 72                 //将原来不是Object类型的elementData数组的内容,赋值给新的Object类型的elementData数组
 73                 elementData = Arrays.copyOf(elementData, size, Object[].class);
 74         } else {
 75             // 其他情况,用空数组代替
 76             this.elementData = EMPTY_ELEMENTDATA;
 77         }
 78     }
 79 
 80     /**
 81      * 修改这个ArrayList实例的容量是列表的当前大小。 应用程序可以使用此操作来最小化ArrayList实例的存储。
 82      */
 83     public void trimToSize() {
 84         modCount++;
 85         if (size < elementData.length) {
 86             elementData = (size == 0)
 87               ? EMPTY_ELEMENTDATA
 88               : Arrays.copyOf(elementData, size);
 89         }
 90     }
 91 //下面是ArrayList的扩容机制
 92 //ArrayList的扩容机制提高了性能,如果每次只扩充一个,
 93 //那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。
 94     /**
 95      * 如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量
 96      * @param   minCapacity   所需的最小容量
 97      */
 98     public void ensureCapacity(int minCapacity) {
 99         //如果是true,minExpand的值为0,如果是false,minExpand的值为10
100         int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
101             // any size if not default element table
102             ? 0
103             // larger than default for default empty table. It's already
104             // supposed to be at default size.
105             : DEFAULT_CAPACITY;
106         //如果最小容量大于已有的最大容量
107         if (minCapacity > minExpand) {
108             ensureExplicitCapacity(minCapacity);
109         }
110     }
111    //得到最小扩容量
112     private void ensureCapacityInternal(int minCapacity) {
113         if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
114               // 获取“默认的容量”和“传入参数”两者之间的最大值
115             minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
116         }
117 
118         ensureExplicitCapacity(minCapacity);
119     }
120   //判断是否需要扩容
121     private void ensureExplicitCapacity(int minCapacity) {
122         modCount++;
123 
124         // overflow-conscious code
125         if (minCapacity - elementData.length > 0)
126             //调用grow方法进行扩容,调用此方法代表已经开始扩容了
127             grow(minCapacity);
128     }
129 
130     /**
131      * 要分配的最大数组大小
132      */
133     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
134 
135     /**
136      * ArrayList扩容的核心方法。
137      */
138     private void grow(int minCapacity) {
139         // oldCapacity为旧容量,newCapacity为新容量
140         int oldCapacity = elementData.length;
141         //将oldCapacity 右移一位,其效果相当于oldCapacity /2,
142         //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
143         int newCapacity = oldCapacity + (oldCapacity >> 1);
144         //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
145         if (newCapacity - minCapacity < 0)
146             newCapacity = minCapacity;
147         //再检查新容量是否超出了ArrayList所定义的最大容量,
148         //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
149         //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。
150         if (newCapacity - MAX_ARRAY_SIZE > 0)
151             newCapacity = hugeCapacity(minCapacity);
152         // minCapacity is usually close to size, so this is a win:
153         elementData = Arrays.copyOf(elementData, newCapacity);
154     }
155     //比较minCapacity和 MAX_ARRAY_SIZE
156     private static int hugeCapacity(int minCapacity) {
157         if (minCapacity < 0) // overflow
158             throw new OutOfMemoryError();
159         return (minCapacity > MAX_ARRAY_SIZE) ?
160             Integer.MAX_VALUE :
161             MAX_ARRAY_SIZE;
162     }
163 
164     /**
165      *返回此列表中的元素数。
166      */
167     public int size() {
168         return size;
169     }
170 
171     /**
172      * 如果此列表不包含元素,则返回 true 。
173      */
174     public boolean isEmpty() {
175         //注意=和==的区别
176         return size == 0;
177     }
178 
179     /**
180      * 如果此列表包含指定的元素,则返回true 。
181      */
182     public boolean contains(Object o) {
183         //indexOf()方法:返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1
184         return indexOf(o) >= 0;
185     }
186 
187     /**
188      *返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1
189      */
190     public int indexOf(Object o) {
191         if (o == null) {
192             for (int i = 0; i < size; i++)
193                 if (elementData[i]==null)
194                     return i;
195         } else {
196             for (int i = 0; i < size; i++)
197                 //equals()方法比较
198                 if (o.equals(elementData[i]))
199                     return i;
200         }
201         return -1;
202     }
203 
204     /**
205      * 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。.
206      */
207     public int lastIndexOf(Object o) {
208         if (o == null) {
209             for (int i = size-1; i >= 0; i--)
210                 if (elementData[i]==null)
211                     return i;
212         } else {
213             for (int i = size-1; i >= 0; i--)
214                 if (o.equals(elementData[i]))
215                     return i;
216         }
217         return -1;
218     }
219 
220     /**
221      * 返回此ArrayList实例的浅拷贝。 (元素本身不被复制。)
222      */
223     public Object clone() {
224         try {
225             ArrayList<?> v = (ArrayList<?>) super.clone();
226             //Arrays.copyOf功能是实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度
227             v.elementData = Arrays.copyOf(elementData, size);
228             v.modCount = 0;
229             return v;
230         } catch (CloneNotSupportedException e) {
231             // 这不应该发生,因为我们是可以克隆的
232             throw new InternalError(e);
233         }
234     }
235 
236     /**
237      *以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。
238      *返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。
239      *因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。
240      */
241     public Object[] toArray() {
242         return Arrays.copyOf(elementData, size);
243     }
244 
245     /**
246      * 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素);
247      *返回的数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则返回其中。
248      *否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。
249      *如果列表适用于指定的数组,其余空间(即数组的列表数量多于此元素),则紧跟在集合结束后的数组中的元素设置为null 。
250      *(这仅在调用者知道列表不包含任何空元素的情况下才能确定列表的长度。)
251      */
252     @SuppressWarnings("unchecked")
253     public <T> T[] toArray(T[] a) {
254         if (a.length < size)
255             // 新建一个运行时类型的数组,但是ArrayList数组的内容
256             return (T[]) Arrays.copyOf(elementData, size, a.getClass());
257             //调用System提供的arraycopy()方法实现数组之间的复制
258         System.arraycopy(elementData, 0, a, 0, size);
259         if (a.length > size)
260             a[size] = null;
261         return a;
262     }
263 
264     // Positional Access Operations
265 
266     @SuppressWarnings("unchecked")
267     E elementData(int index) {
268         return (E) elementData[index];
269     }
270 
271     /**
272      * 返回此列表中指定位置的元素。
273      */
274     public E get(int index) {
275         rangeCheck(index);
276 
277         return elementData(index);
278     }
279 
280     /**
281      * 用指定的元素替换此列表中指定位置的元素。
282      */
283     public E set(int index, E element) {
284         //对index进行界限检查
285         rangeCheck(index);
286 
287         E oldValue = elementData(index);
288         elementData[index] = element;
289         //返回原来在这个位置的元素
290         return oldValue;
291     }
292 
293     /**
294      * 将指定的元素追加到此列表的末尾。
295      */
296     public boolean add(E e) {
297         ensureCapacityInternal(size + 1);  // Increments modCount!!
298         //这里看到ArrayList添加元素的实质就相当于为数组赋值
299         elementData[size++] = e;
300         return true;
301     }
302 
303     /**
304      * 在此列表中的指定位置插入指定的元素。
305      *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大;
306      *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
307      */
308     public void add(int index, E element) {
309         rangeCheckForAdd(index);
310 
311         ensureCapacityInternal(size + 1);  // Increments modCount!!
312         //arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了arraycopy()方法实现数组自己复制自己
313         System.arraycopy(elementData, index, elementData, index + 1,
314                          size - index);
315         elementData[index] = element;
316         size++;
317     }
318 
319     /**
320      * 删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。
321      */
322     public E remove(int index) {
323         rangeCheck(index);
324 
325         modCount++;
326         E oldValue = elementData(index);
327 
328         int numMoved = size - index - 1;
329         if (numMoved > 0)
330             System.arraycopy(elementData, index+1, elementData, index,
331                              numMoved);
332         elementData[--size] = null; // clear to let GC do its work
333       //从列表中删除的元素
334         return oldValue;
335     }
336 
337     /**
338      * 从列表中删除指定元素的第一个出现(如果存在)。 如果列表不包含该元素,则它不会更改。
339      *返回true,如果此列表包含指定的元素
340      */
341     public boolean remove(Object o) {
342         if (o == null) {
343             for (int index = 0; index < size; index++)
344                 if (elementData[index] == null) {
345                     fastRemove(index);
346                     return true;
347                 }
348         } else {
349             for (int index = 0; index < size; index++)
350                 if (o.equals(elementData[index])) {
351                     fastRemove(index);
352                     return true;
353                 }
354         }
355         return false;
356     }
357 
358     /*
359      * Private remove method that skips bounds checking and does not
360      * return the value removed.
361      */
362     private void fastRemove(int index) {
363         modCount++;
364         int numMoved = size - index - 1;
365         if (numMoved > 0)
366             System.arraycopy(elementData, index+1, elementData, index,
367                              numMoved);
368         elementData[--size] = null; // clear to let GC do its work
369     }
370 
371     /**
372      * 从列表中删除所有元素。
373      */
374     public void clear() {
375         modCount++;
376 
377         // 把数组中所有的元素的值设为null
378         for (int i = 0; i < size; i++)
379             elementData[i] = null;
380 
381         size = 0;
382     }
383 
384     /**
385      * 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。
386      */
387     public boolean addAll(Collection<? extends E> c) {
388         Object[] a = c.toArray();
389         int numNew = a.length;
390         ensureCapacityInternal(size + numNew);  // Increments modCount
391         System.arraycopy(a, 0, elementData, size, numNew);
392         size += numNew;
393         return numNew != 0;
394     }
395 
396     /**
397      * 将指定集合中的所有元素插入到此列表中,从指定的位置开始。
398      */
399     public boolean addAll(int index, Collection<? extends E> c) {
400         rangeCheckForAdd(index);
401 
402         Object[] a = c.toArray();
403         int numNew = a.length;
404         ensureCapacityInternal(size + numNew);  // Increments modCount
405 
406         int numMoved = size - index;
407         if (numMoved > 0)
408             System.arraycopy(elementData, index, elementData, index + numNew,
409                              numMoved);
410 
411         System.arraycopy(a, 0, elementData, index, numNew);
412         size += numNew;
413         return numNew != 0;
414     }
415 
416     /**
417      * 从此列表中删除所有索引为fromIndex (含)和toIndex之间的元素。
418      *将任何后续元素移动到左侧(减少其索引)。
419      */
420     protected void removeRange(int fromIndex, int toIndex) {
421         modCount++;
422         int numMoved = size - toIndex;
423         System.arraycopy(elementData, toIndex, elementData, fromIndex,
424                          numMoved);
425 
426         // clear to let GC do its work
427         int newSize = size - (toIndex-fromIndex);
428         for (int i = newSize; i < size; i++) {
429             elementData[i] = null;
430         }
431         size = newSize;
432     }
433 
434     /**
435      * 检查给定的索引是否在范围内。
436      */
437     private void rangeCheck(int index) {
438         if (index >= size)
439             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
440     }
441 
442     /**
443      * add和addAll使用的rangeCheck的一个版本
444      */
445     private void rangeCheckForAdd(int index) {
446         if (index > size || index < 0)
447             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
448     }
449 
450     /**
451      * 返回IndexOutOfBoundsException细节信息
452      */
453     private String outOfBoundsMsg(int index) {
454         return "Index: "+index+", Size: "+size;
455     }
456 
457     /**
458      * 从此列表中删除指定集合中包含的所有元素。
459      */
460     public boolean removeAll(Collection<?> c) {
461         Objects.requireNonNull(c);
462         //如果此列表被修改则返回true
463         return batchRemove(c, false);
464     }
465 
466     /**
467      * 仅保留此列表中包含在指定集合中的元素。
468      *换句话说,从此列表中删除其中不包含在指定集合中的所有元素。
469      */
470     public boolean retainAll(Collection<?> c) {
471         Objects.requireNonNull(c);
472         return batchRemove(c, true);
473     }
474 
475 
476     /**
477      * 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。
478      *指定的索引表示初始调用将返回的第一个元素为next 。 初始调用previous将返回指定索引减1的元素。
479      *返回的列表迭代器是fail-fast 。
480      */
481     public ListIterator<E> listIterator(int index) {
482         if (index < 0 || index > size)
483             throw new IndexOutOfBoundsException("Index: "+index);
484         return new ListItr(index);
485     }
486 
487     /**
488      *返回列表中的列表迭代器(按适当的顺序)。
489      *返回的列表迭代器是fail-fast 。
490      */
491     public ListIterator<E> listIterator() {
492         return new ListItr(0);
493     }
494 
495     /**
496      *以正确的顺序返回该列表中的元素的迭代器。
497      *返回的迭代器是fail-fast 。
498      */
499     public Iterator<E> iterator() {
500         return new Itr();
501     }
502 
503 }

 

九、线程安全性

  1、ArrayList 是线程不安全的

    线程安全可以简单理解为:多个线程同时操作一个方法或变量时,不会出现问题,若出现问题,可认为是线程不安全的。
    ArrayList 是线程不安全的,主要体现有二:
    (1)多个线程往 ArrayList 添加数据时(扩容时),可能会产生数组越界异常(ArrayIndexOutOfBoundsException);
    (2)多个线程遍历同一个 ArrayList,有线程对其进行修改时,可能会抛出 ConcurrentModificationException。
    先对 add() 方法进行分析:
1     public boolean add(E e) {
2         ensureCapacityInternal(size + 1);  // Increments modCount!!
3         elementData[size++] = e;
4         return true;
5     }

 

    注意:i++ 操作是非原子性的。

  2、场景分析一:

    若有一个初始容量为 1 的 ArrayList,线程 T1 和 T2 同时向其中添加元素(add()方法),当添加第2个元素时,需要进行扩容。
    此时若有以下执行时序:
    (1)T1、T2 检测到需要扩容
      此时,T1 和 T2 拿到的都是 elementData.length =1,size = 1,若 T1 先执行 ensureCapacityInternal() 方法扩容,则 elementData.length=2,size=1;之后 T2 再执行 ensureCapacityInternal() 方法时,因为初始 size = 1,而 T1 扩容后 elementData.length = 2,所以 T2 不会再进行扩容(不再执行 grow()方法)。
    (2)T1 执行赋值操作和 size++ 操作
      之后 T1 执行赋值操作 elementData[1]=XX 和 size++,size自增为2。
    (3)T2 执行赋值操作(数组越界)和 size++操作
      由于上一步 T1 执行了 size++操作,当前 size=2,这时的赋值 elementData[size++] 将对 elementData[2] 执行赋值操作,而 elementData.length = 2,最大下标为 1,这时会发生数组越界异常(ArrayIndexOutOfBoundsException)。

 

  3、场景分析二:

    有一个 ArrayList,线程 T1 对其进行遍历;线程 T2 对其遍历,并移除部分元素。
    对 ArrayList 进行遍历时,以 iterator 方法为例,其代码如下:
1 public Iterator<E> iterator() {
2     return new Itr();
3 }
 
    会创建一个内部类 Itr,如下:
 1 private class Itr implements Iterator<E> {
 2     int expectedModCount = modCount;
 3 
 4     // ...
 5     public E next() {
 6         checkForComodification();
 7         int i = cursor;
 8         if (i >= size)
 9             throw new NoSuchElementException();
10         Object[] elementData = ArrayList.this.elementData;
11         if (i >= elementData.length)
12             throw new ConcurrentModificationException();
13         cursor = i + 1;
14         return (E) elementData[lastRet = i];
15     }
16 
17     // 检查是否有其他线程进行结构性修改
18     final void checkForComodification() {
19         if (modCount != expectedModCount)
20             throw new ConcurrentModificationException();
21     }
22 }

 

    而 ArrayList的 add()、remove()等结构性修改的操作都会使 modCount++。因此有:若线程 T1 只对 ArrayList 进行遍历;而线程 T2 对同一个 ArrayList 进行了移除元素操作,则会修改 modCount 的值,导致线程 T1 中 modCount != expectedModCount,从而触发 ConcurrentModificationException。

 

十、总结

  1、ArrayList 可以理解为 【可以自动扩容的数组】,默认初始化容量为 10,默认每次扩容为原容量的 1.5 倍。
  2、扩容时会创建一个新的数组,并将之前的元素拷贝到新数组中(因此,若要将数量已知的元素放入 ArrayList,在初始化时指定长度可以避免多次扩容);
  3、线程不安全,不适合在多线程场景下使用。

 

posted on 2021-04-19 10:30  格物致知_Tony  阅读(357)  评论(0编辑  收藏  举报