ArrayList的部分源码分析

  欢迎加入Java交流群 512878347 ,欢迎关注微信公众号 以文在线 。 

  ArrayList是经常使用的容器,它的底层是采用动态数组实现的,它的容量可以自动增加。ArrayList是一个泛型类,它继承了AbstractList类,实现了List、RandomAccess、Cloneable、Serializable接口,如图1所示。本文从ArrayList的部分源代码入手,详细的讲解了ArrayList的存储结构、改取元素、增删元素等知识。

存储结构

   顺序表(sequence list)是线性表的一种,它是在内存中开辟一段连续的存储空间存放元素,它把逻辑上相邻的元素存储在物理位置上相邻的存储单元中(存储单元也称为节点,每一个节点存放一个元素),元素之间的逻辑关系由节点的邻接关系来体现。 

  数组采用的存储结构是顺序表,ArrayList是使用动态数组实现的。ArrayList内部维护的是一个Object类型的数组elementData,ArrayList的元素就是存储在这个数组中的,参见程序1第11句代码。ArrayList的容量capacity就是数组elementData的长度,ArrayList存储的元素个数size小于等于它的容量capacity,即顺序表中节点的数量大于等于要存储的元素的数量。例如在图2中,ArrayList的元素的个数size为n,容量capacity为n + N,即在内存中开辟了一个节点为n + N的顺序表并用这个顺序表存储了n个元素:第1个节点到第n各节点依次存储了a1、…、ai、…、an,第n + 1个节点到第n + N个节点没有存储元素。没有存储元素的节点预留下来增加新元素。

成员变量

   程序1给出了ArrayList中的成员变量并对这些成员变量进行了说明。 

 1 // 序列化ID
 2 private static final long serialVersionUID = 8683452581122892189L;
 3 // 默认初始容量。
 4 private static final int DEFAULT_CAPACITY = 10;
 5 // 空数组。
 6 private static final Object[] EMPTY_ELEMENTDATA = {};
 7 // 默认容量的空数组。
 8 // 该空数组仅用于在无参构造器中给elementData赋值。
 9 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
10 // 用于存储元素的数组,ArrayList的容量是该数组的长度。
11 transient Object[] elementData;
12 // 元素的个数。
13 private int size;
14 /*
15  * 数组的最大长度。
16  * 少部分虚拟机会在数组中存储头部信息(比如数组的长度),
17  * 头部信息会占用一定的内存,将数组的最大长度
18  * 设置为Integer.MAX_VALUE - 8是最安全的。
19  */
20 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;  

程序1 成员变量

   在ArrayList中还有一个成员变量modCount,该成员变量是从父类AbstractList中继承过来的,它表示列表发生结构上修改的次数,参见程序2。结构上修改是指改变列表中元素的个数,不包含修改元素的值。 

1 // 列表发生结构上修改的次数。
2 protected transient int modCount = 0; 

程序2 成员变量modCount 

构造方法 

  ArrayList类提供了三个构造器:

   1、ArrayList (int initialCapacity):构造一个指定容量的空列表(没有存放元素的列表),参见程序3。 

 1 /**
 2  * 构造一个指定容量的空列表。
 3  * 
 4  * @param initialCapacity 指定容量
 5  * @return IllegalArgumentException 
 6            如果指定的容量为负数
 7  */
 8 public ArrayList(int initialCapacity) {
 9    if (initialCapacity > 0) {
10       this.elementData = new Object[initialCapacity];
11    } else if (initialCapacity == 0) {
12       this.elementData = EMPTY_ELEMENTDATA;
13    } else {
14       throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
15    }
16 }

程序3 构建指定容量的构造器 

  2、ArrayList ():使用默认容量的空数组构建一个空列表,参见程序4。 

1 /**
2 * 使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA构建一个空列表。
3 */
4 public ArrayList() {
5     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
6 }

程序4 默认构造器 

  3、ArrayList (Collection<? extends E> c):构造一个包含给定集合所有元素的列表,并且这些元素在列表中的顺序和给定集合的迭代器返回它们的顺序是一致的,参见程序5。 

 1 /**
 2  * 构造一个包含给定集合所有元素的列表,并且这些元素在
 3  * 列表中的顺序和给定集合的迭代器返回它们的顺序是一致的。
 4  * 
 5  * @param c 给定集合
 6  * @throws NullPointerException 
 7  *         如果给定的集合为空(将null作为参数传递进来)
 8  */
 9 public ArrayList(Collection<? extends E> c) {
10    /*
11     * 将集合转化为数组并赋给elementData。
12     * c.toArray()返回的实际类型(对象类型)可能不是
13     * Object数组。因为Collection的某些实现类(比如:
14     * Arrays$ArrayList),是使用泛型数组来存储元素的,
15     * 这些类的toArray()方法的返回值保留了实际的类型。
16     */
17    elementData = c.toArray();
18    if ((size = elementData.length) != 0) {
19       // elementData实际的类型不是Object数组的时候,
20       // 必须将elementData实际的类型变为Object数组。
21       if (elementData.getClass() != Object[].class)
22          elementData = Arrays.copyOf(elementData, size, Object[].class);
23    } else {
24       // 使用EMPTY_ELEMENTDATA取代集合转换成的数组,
25       // 即将elementData重新赋值为EMPTY_ELEMENTDATA。
26       this.elementData = EMPTY_ELEMENTDATA;
27    }
28 } 

程序5 构建包含给定集合所有元素的构造器 

取改元素 

  在图2中,如果第一个节点的存储地址(首地址)为Loc(a1),每一个节点占用的内存为len,由于顺序表使用连续的存储空间来存放元素,那就可以计算出第i个节点的存储地址,如式(1)所示: 

Loc(ai) = Loc(a1) + (i-1) * len   (1)

  根据式(1)得到地址可以找到第i个节点,然后可以对该节点进行操作,比如:获取节点中的元素,修改节点中的元素等。 

  ArrayList的get (int index)方法就是用来获取节点中的元素,即返回列表中位置(索引)为给定位置(索引)的元素,参见程序6。

 1 /**
 2  * 返回列表中索引为给定索引的元素。
 3  * 
 4  * @param 给定索引
 5  * @return 列表中索引为给定索引的元素
 6  * @throws IndexOutOfBoundsException
 7  *         如果索引小于0或者大于等于size
 8  */
 9 public E get(int index) {
10     // 检查索引是否越界。
11     rangeCheck(index);
12     // 返回给定索引的元素。
13     return elementData(index);
14 }

程序6 get(int index)方法 

  ArrayList中的set (int index, E element)方法就是用来修改节点中的元素,即用给定元素替换列表中索引为给定索引的元素,参见程序7。

 1 /**
 2  * 用给定元素替换列表中索引为给定索引的元素。
 3  * 
 4  * @param index 给定索引
 5  * @param element 给定元素
 6  * @return 被替换的元素
 7  * @throws IndexOutOfBoundsException
 8  *         如果索引小于0或者大于等于size
 9  */
10 public E set(int index, E element) {
11     // 检查索引是否越界。
12     rangeCheck(index);
13     // 存储被替换的元素。
14     E oldValue = elementData(index);
15     // 替换元素。
16     elementData[index] = element;
17     // 返回被替换的元素。
18     return oldValue;
19 } 

程序7 set(int index, E element)方法

   get (int index)方法和set (int index, E element)方法都调用了elementData (index)方法和rangeCheck (index)方法。elementData (int index)方法是以泛型的形式返回elementData数组中索引(下标)为给定索引(下标)的元素,参见程序8;rangeCheck (int index)方法是用来检查下标是否越界,参见程序9,该方法调用了outOfBoundsMsg (index)方法,outOfBoundsMsg (int index)方法用来生成越界信息,参见程序10。

1  /**
2   * 以泛型的形式返回elementData数组中下标为给定下标的元素。
3   */
4 E elementData(int index) 
5     return (E) elementData[index];
6 }

程序8 elementData(int index)方法 

 1 /**
 2  * 检查下标是否越界。
 3  * 该方法没有检查下标为负数的情况,
 4  * 当下标为负数时,通过该下标访问数组
 5  * 会抛出数组下标越界异常,
 6  * 数组下标越界异常也是下标越界异常。
 7  * 
 8  * @param 下标
 9  */
10 private void rangeCheck(int index) {
11     if(index >= size) {
12         throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
13     }
14

程序9 rangeCheck(int index)方法 

1 /**
2  * 生成越界信息。
3  * 
4  * @param index 下标
5  * @return 越界信息
6  */
7 private String outOfBoundsMsg(int index) {
8     return "Index: "+index+", Size: "+size;
9 }

程序10 outOfBoundsMsg(int index)方法 

增删元素 

  如图3所示,要向一个容量capacity (elementData. length)为n + N,元素个数size为n(这n个元素依次是:a1、…、ai-1、ai、…、an)的ArrayList中第i个节点处插入M个元素(这M个元素依次是b1、…、bM)。这时至少需要n + M个节点来存储全部元素(原有的n个元素和将要插入的M个元素),将n + M称为最小容量,用minCapacity来表示。向ArrayList中插入元素分为了5步:

 

  1、判断ArrayList是否是通过无参构造器初始化的并且从来没有进行扩容。如果是的话,就将minCapacity设置为n + M和DEFAULT_ CAPACITY(参见程序1中第 4句代码)中较大的值,参见程序11。这样能够保证通过无参构造器创建的ArrayList对象扩容后的最小容量为DEFAULT_ CAPACITY。  

 1 /**
 2  * 确保内部容量。
 3  * 
 4  * @param minCapacity 最小容量
 5  */
 6 private void ensureCapacityInternal(int minCapacity) {
 7     if(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
 8         minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
 9     }
10     // 调用ensureExplicitCapacity(minCapacity)
11     // 方法确保列表的容量够用。
12     ensureExplicitCapacity(minCapacity);
13 }

程序11 ensureCapacityInternal(int minCapacity)方法 

  2、判断是否需要扩容,参见程序12。即判断式(2)是否成立,如果式(2)成立,就需要扩容,进入第3步;如果式(2)不成立,就不需要扩容,直接进入第4步。

 1 /**
 2  * 确保清晰的容量。
 3  * 
 4  * @param minCapacity 最小容量
 5  */
 6 private void ensureExplicitCapacity(int minCapacity) {
 7     modCount++;
 8     if(minCapacity - elementData.length > 0) {
 9          grow(minCapacity);
10     }
11 }

程序12 ensureExplicitCapacity(int minCapacity)方法 

  需要注意的是,式(2)不能写成式(3),即程序12里面第 8句代码中if语句的逻辑表达式不能写成minCapacity > elementData. length,因为前者考虑了溢出问题,后者没有考虑溢出问题。二者的差别通过一个例子说明,比如:minCapacity为Integer. MIN_ VALUE,capacity为Integer. MAX_ VALUE。计算机中使用二进制补码存储数值信息,那Integer. MIN_ VALUE - Integer. MAX_ VALUE等于1,故式(2)是成立的,而式(3)是不成立的。 

minCapacity – elementData.length > 0   (2)

minCapacity > elementData.length   (3)

   3、增加ArrayList的容量(扩容),参见程序13。先将ArrayList的容量扩大1.5倍,得到新的容量newCapacity,然后判断newCapacity - minCapacity < 0(不能写成newCapacity < minCapacity)是否成立。如果成立就将newCapacity设置为minCapacity,图3演示就是这种情况。然后创建一个长度为newCapacity的数组,将elementData指向数组中的全部元素依次存储到新数组的前n个元素中,并将该新数组作为ArrayList内部维护的数组,即将elementData指向新数组。

 1 /**
 2  * 扩容,以确保该数组至少能够存储minCapacity个元素。
 3  * 
 4  * @param minCapacity 最小容量
 5  */
 6 private void grow(int minCapacity) {
 7     // 旧容量。
 8     int oldCapacity = elementData.length;
 9     /*
10      * 先扩容一半,即将容量变为原来的1.5倍。
11      * >>是移位运算,oldCapacity >> 1等价于
12      * oldCapacity / 2,前者效率高于后者。
13      */
14     int newCapacity = oldCapacity + (oldCapacity >> 2);
15     // 如果扩容1.5后的新容量仍然不够用,
16     // 继续扩容将新容量设定为最小容量。
17     if(newCapacity - minCapacity < 0) {
18         newCapacity = minCapacity;
19     }
20     // 调用hugeCapacity(minCapacity)方法。
21     if(newCapacity - MAX_ARRAY_SIZE > 0) {
22         newCapacity = hugeCapacity(minCapacity);
23     }
24     // 创建新数组并复制元素。
25     elementData = Arrays.copyOf(elementData, newCapacity);
26 } 

程序13 grow(int minCapacity)方法 

  要注意的是,在扩容时得到的新容量newCapacity不能超过数组所允许的最大长度,这是通过调用hugeCapacity (minCapacity)方法来保证的。hugeCapacity (int minCapacity)方法的作用是分情况获取elementData的最大长度,参见程序14。

 1 /**
 2  * 分情况获取elementData的最大长度。
 3  * 
 4  * @param minCapacity 最小容量
 5  * @return 最大长度
 6  */
 7 private static int hugeCapacity(int minCapacity) {
 8     /*
 9      * 最小容量超过Integer.MAX_VALUE(溢出),
10      * 则抛出内存溢出错误。
11      * 这里minCapacity超过Integer.MAX_VALUE后
12      * 一定得到负数,这是因为minCapacity不会超
13      * 出Integer.MAX_VALUE很多,最多超出
14      * Integer.MAX_VALUE一倍,故minCapacity不
15      * 可能超出Integer.MAX_VALUE后又回到正数。
16      */
17     if(minCapacity < 0) {
18         throw new OutOfMemoryError();
19     }
20     // 最小容量大于MAX_ARRAY_SIZE,
21     // 返回Integer.MAX_VALUE,否则返回MAX_ARRAY_SIZE。
22     return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
23 }

程序14 hugeCapacity(int minCapacity)方法 

  4、移动旧元素,即将elementData数组中下表为i - 1的元素(第i个节点中的元素)及其以后的元素依次向后移动M个位置,即将元素ai、ai+1、…、an依次存储到第i + M个节点、第i + 1 + M个节点、…、第n + M个节点中。 

  5、插入新元素,即将要插入的M个元素b1、…、bM依次插入到第i个节点、…、第i - 1 + M个节点中。 

  ArrayList类提供了四个增加元素的方法: 

  1、add (E e):追加给定元素到列表的末尾,这时不需要移动旧元素,即不需要上述步骤的第4步,参见程序15。

 1 /**
 2  * 追加给定元素到列表的末尾。
 3  * 
 4  * @param e 追加的元素。
 5  * @return <tt>true</tt> 
 6  *         (被{@link Collection#add}规定)
 7  */
 8 public boolean add(E e) {
 9     /*
10      * 确保列表有足够的容量来存放要追加的元素。
11      * 要在列表中追加一个元素,
12      * 数组elementData所需要的最小容量为size+1。
13      */
14     ensureCapacityInternal(size + 1);
15     // 将e追加到列表的末尾并对列表元素的个数进行自增。
16     elementData[size++] = e;
17     return true;
18

程序15 add(E e)方法 

  2、addAll (Collection<? extends E> c):将给定集合的所有元素按照集合的迭代器返回的顺序依次追加到列表的末尾,参见程序16。

 1 /**
 2  * 将给定集合的所有元素按照集合的迭代器
 3  * 返回的顺序依次追加到列表的末尾。
 4  * 执行该方法的时候不允许给定集合发生改变。
 5  * 如果当前列表不为空集,不能将它自身作为该方法的参数。
 6  * 
 7  * @param c 给定集合
 8  * @return <tt>true</tt> 如果当前列表发生改变
 9  * @throws IndexOutOfBoundsException
10  *         如果下标小于0或者大于size
11  * @throws NullPointerException 如果给定集合为空
12  */
13 public boolean addAll(Collection<? extends E> c) {
14     // 将给定集合转换为数组
15     Object[] a = c.toArray();
16     // 获取a的长度(给定集合的元素个数)。
17     int numNew = a.length;
18     /*
19      * 确保列表有足够的容量来存放要追加的元素。
20      * 要在列表中追加numNew个元素,
21      * 数组elementData所需要的最小容量为size+numNew。
22      */
23     ensureCapacityInternal(size + numNew);
24     // 将数组a的所有元素追加到elementData数组中。
25     System.arraycopy(a, 0, elementData, size, numNew);
26     // 修改列表中元素的个数。
27     size += numNew;
28     // numNew != 0 代表集合发生了改变。
29     return numNew != 0;
30 }

程序16 addAll(Collection<? extends E> c)方法 

  3、add (int index, E element):在列表中索引为给定索引处插入给定元素,参见程序17。  

 1 /**
 2  * 在列表中索引为给定索引处插入给定元素。
 3  * 移动列表中索引为给定索引的元素(如果有的话)
 4  * 及其以后的元素到它的右边(将它们的下标加1)。
 5  * 
 6  * @param index 给定索引。
 7  * @param element 要插入的元素。
 8  * @throws IndexOutOfBoundsException
 9  *         如果下标小于0或者大于size
10  */
11 public void add(int index, E element) {
12     //检查索引是否越界。
13     rangeCheckForAdd(index);
14     // 确保列表有足够的容量来存放要插入的元素。
15     ensureCapacityInternal(size + 1);
16     // 将列表中索引为给定索引的元素
17     // 及其以后的元素依次向后移动一个位置。
18     System.arraycopy(elementData, index, elementData, index+1, size-index);
19     // 将给定元素插入到指定位置。
20     elementData[index] = element;
21     // 对元素的个数进行自增。
22     size++;
23 } 

程序17 add(int index, E element)方法 

  4、addAll (int index, Collection<? extends E> c):将给定集合的所有元素按照集合的迭代器返回的顺序依次从索引为给定索引处插入到列表中,参见程序18。

 1 /**
 2  * 将给定集合的所有元素按照集合的迭代器返回的顺序
 3  * 依次从索引为给定索引处插入到列表中。
 4  * 移动列表中索引为给定索引的元素(如果有的话)
 5  * 及其以后的元素到它的右边(增加它们的下标)。
 6  * 
 7  * @param index 给定集合的第一个元素插入的位置
 8  * @param c 给定集合
 9  * @return <tt>true</tt> 如果当前列表发生改变
10  * @throws NullPointerException 如果给定集合为空
11  */
12 public boolean addAll(int index, Collection<? 
13 extends E> c) {
14     //检查索引是否越界。
15     rangeCheckForAdd(index);
16     Object[] a = c.toArray();
17     int numNew = a.length;
18     // 确保列表有足够的容量来存放要插入的元素。
19     ensureCapacityInternal(size + numNew);
20     // 要移动的元素的个数。
21     int numMoved = size - index;
22     // 如果有要移动的元素,
23     // 将这些元素依次向后移动numNew个位置。
24     if(numMoved > 0) {
25         System.arraycopy(elementData, index, elementData, index + numNew, numMoved); 
26     }
27     // 将数组a的所有元素插入到elementData数组中。
28     System.arraycopy(a, 0, elementData, index, numNew);
29     size += numNew;
30     return numNew != 0;
31 } 

程序18 addAll(int index, Collection<? extends E> c)方法 

  add (int index, E element)方法和addAll (int index, Collection<? extends E> c)方法都调用了rangeCheckForAdd (index)方法,该方法是专门为add (int index)和addAll (inde index, Collection<? extends E> c)方法提供的用来检查下标是否越界,参见程序19。该方法也调用了outOfBoundsMsg (index)方法(参见程序10)生成越界信息。

 1 /**
 2  * 专门为add(int index)和addAll(index index, 
 3  * Collection<? extends E> c)方法提供的检查下标是否越界。
 4  * 
 5  * @param index 下标
 6  */
 7 private void rangeCheckForAdd(int index) {
 8     if(index > size || index <0) {
 9         throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
10     }
11 } 

程序19 rangeCheckForAdd(int index)方法 

  如图4所示,要从一个容量capacity为n + N,元素的个数为n(这n个元素依次是:a1、…、ai-1、ai、ai+1、…、an)的ArrayList中删除与给定元素bi相同的元素分为了2步: 

  1、在当前列表中查找与给定元素bi相同的元素。如果在当前列表中找到了与给定元素相同的元素,进入第2步;如果在当前列表中没有找到与给定元素相同的元素,就结束操作,这时没有删除任何元素。 

  2、将与给定元素bi相同的元素ai从列表中删除,可以通过将要删除元素后面的所有元素依次向前移动一个位置来实现。

  ArrayList类提供了三个删除元素的方法: 

  1、remove (int index):删除列表中索引为给定索引的元素,这时不需要查找元素,即不需要上述步骤的第1步,参见程序20。该方法调用了rangeCheck (index)方法(参见程序9)来检查下标是否越界。

 1 /**
 2  * 删除列表中索引为给定索引的元素。
 3  * 移动列表中索引为给定索引以后的
 4  * 所有元素到它的左边(将它们的下标减去1)。
 5  * 
 6  * @param index 给定索引
 7  * @return 被删除的元素
 8  * @throws IndexOutOfBoundsException
 9  *         如果下标小于0或者大于等于size
10  */
11 public E remove(int index) {
12     // 检查索引是否越界。
13     rangeCheck(index);
14     // 对modCount自增。
15     modCount++;
16     // 获取删除的元素。
17     E oldValue = elementData(index);
18     // 要移动的元素的个数。
19     int numMoved = size - index -1;
20     // 如果有要移动的元素,将这些元素依次向前移动一个位置。
21     if(numMoved > 0)
22         System.arraycopy(elementData, index + 1, elementData, index, numMoved);
23     // 将列表的最后一个元素赋值为null
24     // 并对列表元素的个数进行自减。
25     elementData[--size] = null;
26     // 返回删除的元素。
27     return oldValue;
28 } 

程序20 remove(int index)方法 

  2、remove (Object o):删除列表中与给定元素相同的第一个元素(最多只删除一个元素),如果列表中没有和给定元素相同的元素,就不进行操作,参见程序21。

 1 /**
 2  * 删除列表中与给定元素相同的第一个元素。
 3  * 如果列表中没有和给定元素相同的元素,就不进行操作。
 4  * 
 5  * @param o 给定元素
 6  * @return <tt>true</tt> 如果从列表中删除了元素
 7  */
 8 public boolean remove(Object o) {
 9     if( o == null) {    // 给定元素为null。
10         // 遍历集合的所有元素。
11         for(int index=0;index<size;index++) {
12             // 如果集合的某一个元素为null,调用
13             // fastRemove(index)删除该元素并返回true。
14             if(elementData[index] == null) {
15                 fastRemove(index);
16                 return true;
17             }
18         }
19     } else {    //给定元素不为null。
20         // 遍历集合的所有元素。
21         for(int index=0;index<size;index++) {
22             // 如果集合的某一个元素和给定元素相同,调用
23             // fastRemove(index)删除该元素并返回true。
24             if(o.equals(elementData[index])) {
25                 fastRemove(index);
26                 return true;
27             }
28         }
29     }
30     // 程序执行到这里,集合中一定不包含给定元素。
31     return false;
32 }

程序21 remove(Object o)方法

   在remove (Object o)方法中是调用fastRemove (int index)方法,该方法是专门为remove (Object o)提供的用来快速删除元素,参见程序22。该方法与remove (int index)方法(参见程序20)的区别是:该方法不进行下标越界检查、不返回删除的旧元素。

 1 /**
 2  * 快速删除元素的方法,不进行下标越界检查,
 3  * 不返回删除的旧元素。
 4  */
 5 private void fastRemove(int index) {
 6     modCount++;
 7     int numMoved = size - index -1;
 8     if(numMoved > 0)
 9         System.arraycopy(elementData, index + 1, elementData, index, size – 1 - index);
10     elementData[--size] = null;
11 } 

程序22 fastRemove(int index)方法 

  3、removeAll (Collection<?> c):删除列表中所有被给定集合包含的元素,参见程序23。

 1 /**
 2  * 删除列表中所有被给定集合包含的元素。
 3  * 
 4  * @param c 给定集合
 5  * @return {@code true} 如果当前列表发生改变
 6  * @throws ClassCastException 
 7  *         如果当前列表中元素的类型
 8  *         和给定集合元素的类型不匹配
 9  * @throws NullPointException 
10  *         如果当前列表中包含null
11  *         给定集合不允许包含null
12  * @see Collection#contains(Object)
13  */
14 public boolean removeAll(Collection<?> c) {
15     // 判断c是否为null,如果c为空会抛出空指针异常。
16     Objects.requireNonNull(c);
17     // 调用batchRemove(c, false)方法
18     // 删除被给定集合包含的元素。
19     return batchRemove(c, false);
20 } 

程序23 removeAll(Collection<?> c)方法 

  在removeAll (Collection<?> c)方法中调用了batchRemove (Collection<?> c, boolean complement)方法,该方法是根据标识(传递的参数complement)删除或保留列表中被给定集合包含的元素,参见程序24。如果complement为false,删除被给定集合包含的元素(保留没有被给定集合包含的元素);如果complement为true,保留被给定集合包含的元素。

 1 /**
 2  * 根据给定标识删除或保留列表中被给定集合包含的元素。
 3  * 
 4  * @param c 给定集合
 5  * @param complement 给定标识
 6  * @return {@code true} 如果当前列表发生改变
 7  */
 8 private boolean batchRemove(Collection<?> c,boolean complement) {
 9     // 获取集合内部维护的数组。
10     final Object[] elementData = this.elementData;
11     // 数组下标变量r,列表新的元素个数w。
12     int r = 0, w = 0;
13     // 定义变量记录列表是否发生改变。
14     boolean modified = false;
15     try {
16         for(;r<size;r++)
17             /*
18              * 如果complement为false,
19              * 删除被给定集合包含的元素;
20              * 如果complement为true,
21              * 保留被给定集合包含的元素。
22              * 
23              * c.contains可能会抛出类型转换异常或空指针异常。
24              */
25             if(c.contains(elementData[r]) == complement)
26                 elementData[w++] = elementData[r];
27         } finally {
28             // r!=size表示执行try里面的代码抛出了异常。
29             if(r != size) {
30                 // 将抛出异常处的元素及其以后的元素保存下来。
31                 System.arraycopy(elementData, r, elementData, w, size - r);
32                 w += size -r;
33             }
34             // w!=size 表示列表发生了改变。
35             if(w != size) {
36                 // 将下标为w到size-1的元素赋值为null。
37                 for(int i =w;i<size;i++)
38                     elementData[i] = null;
39                 // 修改modCount,删除的元素个数
40                 // 就是从结构上修改列表的次数。
41                 modCount += size - w;
42                 size = w;
43                 modified = true;
44             }
45         }
46     return modified;
47 }

程序24 batchRemove(Collection<?> c, boolean complement)方法 

总结

   1、ArrayList不是线程安全的,它允许元素的值为null,使用时需要注意。 

  2、如果事先可以预知数据量的大小,可以通过public ArrayList (int initialCapacity)构造方法来指定集合的大小,以减少扩容次数,提高写入效率。 

  3、取改元素通过数组下标进行,时间复杂度较低;增删元素会导致数组复制,时间复杂度较高。故在数据量比较大的情况下,取改元素较多,增删元素使用较少,适合使用ArrayList。 

posted @ 2019-01-18 14:44  以文在线  阅读(227)  评论(0)    收藏  举报