ArrayList源码解析

来源:互联网 发布:安庆2017网络效应答案 编辑:程序博客网 时间:2024/05/22 16:02

1. ArrayList概述

ArrayList我们已经非常熟悉了,平时工作中使用最多的集合就是它。用了这么久,还没好好研究研究它的代码,虽然简单,还是有必要好好学习下。

ArrayList用数组作为内部元素的存储,该数组的大小就是ArrayList的容量,它内部还维护了一个变量size保存当前元素的个数。ArrayList根据当前元素的个数和容量,自动扩充数组容量,通过创建一个更大的数组并将旧的数组元素拷贝到新的数组实现。

ArrayList可以存储任何类型的值,包括一个或多个null值。

另外,ArrayList是非线程安全的,如果多线程并发访问ArrayList,必须加锁。

好了,以上是ArrayList的基本介绍,下面我们开始详细看源码,首先看几个构造函数的实现:

2. 构造函数

//该数组作为ArrayList的内部存储,因此我们说ArrayList本质上是一个数组,该数组的大小就是//ArrayList的容量(capacity)private transient Object[] elementData;//所有空的数组共享该空数组实例private static final Object[] EMPTY_ELEMENTDATA = {};public ArrayList(int initialCapacity) {    super();    if (initialCapacity < 0)      throw new IllegalArgumentException("Illegal Capacity: "+                                         initialCapacity);    //创建大小为initialCapacity的数组    this.elementData = new Object[initialCapacity];}public ArrayList() {    super();    //指向空数组    this.elementData = EMPTY_ELEMENTDATA;}public ArrayList(Collection<? extends E> c) {    elementData = c.toArray();    size = elementData.length;    if (elementData.getClass() != Object[].class)      elementData = Arrays.copyOf(elementData, size, Object[].class);}

其中elementData正是作为内部存储的数组,EMPTY_ELEMENTDATA作为所有空数组共享的实例。

参数为initialCapacity的构造函数,创建大小为该参数的内部数组。

无参构造函数直接将引用指向了空数组实例,以前使用ArrayList的时候都会用该无参构造函数,这其实是不妥的,因为这是创建一个内部数组大小为0的数组,当我们往该ArrayList增加元素时,肯定会扩充该数组,因此我们最好还是通过带初始容量的构造函数来创建ArrayList来避免多余的扩容过程。

第三个构造函数通过指定集合来创建ArrayList,同时更新size。这里需要注意的是,Collection的toArray方法返回的数组类型可能不是Object[],而是其他类型,比如是String[]。虽然toArray方法返回数组可以用Object数组引用接收,但是此时若向该数组添加元素,就可能抛出异常,比如如下代码:

package com.lms.test;public class Test {    /**     * 会返回一个元素类型为String的数组     * @return     */    public static String[] arrStr() {        return new String[]{"hello", "java"};    }    public static void main(String[] args) {        Object[] objs = arrStr();        System.out.println(objs.getClass());        //objs数组实际存储的元素是String,若往该元素添加Object对象,相当于将Object强制转型为String,        //显然,向下转型不安全,会抛出java.lang.ArrayStoreException异常        objs[0] = new Object();    }}

打印结果为:

class [Ljava.lang.String;Exception in thread "main" java.lang.ArrayStoreException: java.lang.Object    at com.lms.test.Test.main(Test.java:18)

说白了,试图往存储子类的数组中添加父类元素,会抛出异常,因为向下转型是不安全的。

3. add方法

ArrayList一共有4个添加方法,两个add方法,两个addAll方法。先看下第一个add方法:

public boolean add(E e) {    ensureCapacityInternal(size + 1);    elementData[size++] = e;    return true;}

第一个add方法往数组后面添加元素,添加元素之前需要确保容量最小为size+1,否则该元素无法存储到数组中。看下ensureCapacityInternal方法:

private void ensureCapacityInternal(int minCapacity) {    //如果当前数组是一个空的数组,确保数组容量最小为默认大小(10)    if (elementData == EMPTY_ELEMENTDATA) {      minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);    }    ensureExplicitCapacity(minCapacity);}//结构性改变的次数protected transient int modCount = 0;private void ensureExplicitCapacity(int minCapacity) {    //结构性改变,加1    modCount++;    if (minCapacity - elementData.length > 0)      grow(minCapacity);}

ensureCapacityInternal方法又调用了ensureExplicitCapacity方法,这里先注意下modCount成员变量。它表示对ArrayList结构性改变的次数,所谓结构性改变,即改变数组的大小,每次有结构性改变,都会将modCount值加1。modCount用于ArrayList的 “fail-fast”策略,利用迭代器遍历ArrayList的时候,如果modCount改变了,会抛出ConcurrentModificationException异常。

接着看下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);}

grow是真正扩充数组的地方,扩充后的ArrayList容量至少为minCapacity,每次扩充将容量增加原来容量的一半,并将原来数组的元素拷贝到新的数组。

好了,回到add方法,再确保数组可以存储要添加的元素后,将该元素添加到数组最后位置,并将size加1。

继续看第二个add方法:

public void add(int index, E element) {    //检查添加位置的索引    rangeCheckForAdd(index);    //确保数组有足够空间    ensureCapacityInternal(size + 1);     //将index及其后面的元素往后移动一个元素,腾出位置给元素element    System.arraycopy(elementData, index, elementData, index + 1, size - index);    elementData[index] = element;    size++;}

该方法在数组指定位置处添加指定元素,因为底层通过数组存储,往中间插入一个元素前需要将该元素以及后面的元素往后移动一个位置。这种方式添加元素是很低效的,如果数组元素个数很多,移动的元素可能非常多。

接着看下第一个addAll方法:

public boolean addAll(Collection<? extends E> c) {    Object[] a = c.toArray();    int numNew = a.length;    //确保数组至少还能容下numNew个元素    ensureCapacityInternal(size + numNew);    System.arraycopy(a, 0, elementData, size, numNew);    size += numNew;    return numNew != 0;}

该方法将指定的集合元素全都添加到该ArrayList的末尾,逻辑非常简单,不再说明。

继续看第二个addAll方法:

public boolean addAll(int index, Collection<? extends E> c) {    rangeCheckForAdd(index);    Object[] a = c.toArray();    int numNew = a.length;    ensureCapacityInternal(size + numNew);    int numMoved = size - index;    if (numMoved > 0)      System.arraycopy(elementData, index, elementData, index + numNew,                       numMoved);    System.arraycopy(a, 0, elementData, index, numNew);    size += numNew;    return numNew != 0;}

该方法将指定的集合元素添加到指定位置开始的连续位置。先将该位置开始的numMoved个元素往后移动numNew个位置。再将指定集合元素添加到中间腾出的连续位置。

4. remove方法

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);    //let gc do its work    elementData[--size] = null;    //返回旧值    return oldValue;}

该方法删除指定位置处的元素,返回该位置删除前的值。

继续看下remove另一个重载方法:

public boolean remove(Object o) {    if (o == null) {      for (int index = 0; index < size; index++)        if (elementData[index] == null) {          fastRemove(index);          return true;        }    } else {      for (int index = 0; index < size; index++)        if (o.equals(elementData[index])) {          fastRemove(index);          return true;        }    }    return false;}

该方法删除指定的元素,注意,是删除第一个在数组中出现的该元素(数组可以保存同一个对象的多个引用)。没有其他什么好的方法,只能遍历数组。

再看下清空ArrayList的方法:

public void clear() {    modCount++;    for (int i = 0; i < size; i++)      elementData[i] = null;    size = 0;}

清空ArrayList方法很简单,遍历数组并将元素设置为空,元素个数size设置为0。

继续看下删除指定范围元素的方法:

protected void removeRange(int fromIndex, int toIndex) {    modCount++;    int numMoved = size - toIndex;    System.arraycopy(elementData, toIndex, elementData, fromIndex,                     numMoved);    int newSize = size - (toIndex-fromIndex);    for (int i = newSize; i < size; i++) {      elementData[i] = null;    }    size = newSize;}

该方法删除fromIndex(包含)到toIndex(不包含)索引处的元素。

继续看下removeAll方法:

//删除在集合c中存在的元素public boolean removeAll(Collection<?> c) {    return batchRemove(c, false);}//删除在集合c中不存在的元素public boolean retainAll(Collection<?> c) {    return batchRemove(c, true);}//该方法有两个意义://1. 删除本List中存在于指定集合c的元素,对应complement=false//2. 删除本List中不存在于指定集合c的元素,对应complement=trueprivate boolean batchRemove(Collection<?> c, boolean complement) {    final Object[] elementData = this.elementData;    int r = 0, w = 0;    boolean modified = false;    try {      //elementData中存储最终需要保留的元素      for (; r < size; r++)        //如果complement=true,说明需要保留存在于集合c的元素        //如果complement=false,说明需要保留不存在于集合c的元素        if (c.contains(elementData[r]) == complement)          elementData[w++] = elementData[r];    } finally {      //c.contains可能抛出异常,因此r可能不等于size      if (r != size) {        System.arraycopy(elementData, r,                         elementData, w,                         size - r);        w += size - r;      }      if (w != size) {        for (int i = w; i < size; i++)          elementData[i] = null;        modCount += size - w;        size = w;        modified = true;      }    }    return modified;}

5. 序列化方法

ArrayList实现了Serializable接口,并且添加了readObject和writeObject方法,先看下writeObject方法:

private void writeObject(java.io.ObjectOutputStream s)  throws java.io.IOException{    int expectedModCount = modCount;    //将ArrayList非静态和非transient的字段写到输出流    s.defaultWriteObject();    //元素个数写入到输出流    s.writeInt(size);    //遍历数组,按序写入到输出流    for (int i=0; i<size; i++) {      s.writeObject(elementData[i]);    }    //有可能写入过程中,其他线程修改了本List    if (modCount != expectedModCount) {      throw new ConcurrentModificationException();    }}

该序列化方法首先通过defaultWriteObject方法将一些隐藏的值写入输出流,接着将ArrayList元素的个数写入输出流,最后遍历数组按序将每个元素都写出到输出流。方法最后,如果发现modCount值变了,抛出并发修改异常。

接着看下readObject方法:

private void readObject(java.io.ObjectInputStream s)  throws java.io.IOException, ClassNotFoundException {    elementData = EMPTY_ELEMENTDATA;    //从输入流读取非静态和非transient的字段    s.defaultReadObject();    //从输入流中读取元素个数    s.readInt();    if (size > 0) {      //分配空间      ensureCapacityInternal(size);      Object[] a = elementData;      for (int i=0; i<size; i++) {        a[i] = s.readObject();      }    }}

该方法其实就是writeObject的反方法,先读取非静态和非transient的字段值,然后读取元素个数,最后分配空间并按序从输入流读取元素放到数组中。

6. 迭代器

ArrayList通过内部类实现了迭代器,看下源码:

//迭代器实现接口Iteratorprivate class Itr implements Iterator<E> {    //cursor表示下一个遍历元素的位置    int cursor;          //最近一次返回元素的位置    int lastRet = -1;     //如果迭代过程中发现expectedModCount不等于modCount,抛出并发修改异常    int expectedModCount = modCount;    public boolean hasNext() {      //如果当前位置不等于元素的个数,说明还有下一个元素      return cursor != size;    }    //返回下一个元素    public E next() {      //检查expectedModCount,如果不等于modCount,抛出ConcurrentModificationException异常      checkForComodification();      int i = cursor;      if (i >= size)        throw new NoSuchElementException();      Object[] elementData = ArrayList.this.elementData;      if (i >= elementData.length)        throw new ConcurrentModificationException();      //返回下一个元素前,更新cursor值      cursor = i + 1;      return (E) elementData[lastRet = i];    }    //通过迭代器删除之前,必须调用迭代器的next方法    //调用一次next方法,最多删除一次,多次删除会抛出异常    public void remove() {      if (lastRet < 0)        throw new IllegalStateException();      checkForComodification();      try {        //真正删除还是委托给了外部类ArrayList        ArrayList.this.remove(lastRet);        cursor = lastRet;        //删除后,重置lastRet为-1,下次删除之前必须再次调用next方法        lastRet = -1;        //删除元素的时候modCount已经改变,重置expectedModCount值为modCount        expectedModCount = modCount;      } catch (IndexOutOfBoundsException ex) {        throw new ConcurrentModificationException();      }    }    //检查expectedModCount是否等于modCount,如果不等于,抛出并发修改异常    final void checkForComodification() {      if (modCount != expectedModCount)        throw new ConcurrentModificationException();    }}

迭代器非常简单,这里需要的注意的是,通过迭代器删除元素的时候,最多连续一次删除操作,而不能连续多次删除操作,迭代器的next操作后面最多只有一次删除操作。

ArrayList还提供了ListIterator的实现。listIterator提供了对List的返回遍历操作,看下ArrayList实现的ListItr:

//ListItr提供了反向遍历的操作private class ListItr extends Itr implements ListIterator<E> {    ListItr(int index) {      super();      cursor = index;    }    //返向遍历是否还有更多的元素    public boolean hasPrevious() {      return cursor != 0;    }    //下个元素位置索引    public int nextIndex() {      return cursor;    }    //返向遍历下一个元素索引    public int previousIndex() {      return cursor - 1;    }    //反向遍历下一个元素    public E previous() {      checkForComodification();      int i = cursor - 1;      if (i < 0)        throw new NoSuchElementException();      Object[] elementData = ArrayList.this.elementData;      if (i >= elementData.length)        throw new ConcurrentModificationException();      cursor = i;      return (E) elementData[lastRet = i];    }    //上一次调用next或者previous返回的元素替换为指定的值    public void set(E e) {      if (lastRet < 0)        throw new IllegalStateException();      checkForComodification();      try {        //调用ArrayList的set方法设置        ArrayList.this.set(lastRet, e);      } catch (IndexOutOfBoundsException ex) {        throw new ConcurrentModificationException();      }    }    //和set方法类似,该方法不是替换,而是在该位置插入一个值    public void add(E e) {      checkForComodification();      try {        int i = cursor;        ArrayList.this.add(i, e);        cursor = i + 1;        lastRet = -1;        expectedModCount = modCount;      } catch (IndexOutOfBoundsException ex) {        throw new ConcurrentModificationException();      }    }}

7. 子列表

ArrayList提供了方法subList()来获取ArrayList一个视图。返回的视图类型是ArrayList的内部类SubList,注意,该类只是ArrayList的一个视图,任何对该视图的修改都会反映到ArrayList,相反也是。这部分比较简单,不再详细讲解,主要看下SubList的定义:

private class SubList extends AbstractList<E> implements RandomAccess {    private final AbstractList<E> parent;    private final int parentOffset;    private final int offset;    int size;    //……}

其中parent指向外部类ArrayList的实例。

parentOffset表示该视图从原List的截取位置,注意要配合offset使用,在子列表中访问元素的时候都要加上offset这个偏移。

size表示本视图的元素个数。

原创粉丝点击