ArrayList

234 阅读6分钟

ArrayList总结

(1)ArrayList是一个容量能动态增加的动态数组,使用默认构造方法初始化出来的容量是10。

(2)ArrayList 允许空值和重复元素,当往 ArrayList 中添加的元素数量大于其底层数组容量时,其会通过扩容机制重新生成一个更大的数组。ArrayList扩容的长度是原长度的1.5倍

(3)ArrayList 是非线程安全类。

成员变量和构造函数

继承体系和成员变量

(1)ArrayList 继承于AbstractList,实现了List,表明它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

(2)ArrayList 实现了标志接口RandomAccess,表明它支持快速随机访问。

(3)ArrayList 实现了Cloneable 接口,覆盖了函数 clone(),能被克隆。

(4)ArrayList 实现java.io.Serializable 接口,表明它能通过序列化传输。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
    // 默认初始容量大小
    private static final int DEFAULT_CAPACITY = 10;

    //空数组(用于空实例)
    private static final Object[] EMPTY_ELEMENTDATA = {};

     //用于默认大小空实例的共享空数组实例。
      //我们把它从EMPTY_ELEMENTDATA数组中区分出来,
      //以知道在添加第一个元素时容量需要增加多少。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //保存ArrayList数据的数组
    transient Object[] elementData;  

    //ArrayList 所包含的元素个数
    private int size;
    //要分配的最大数组大小
     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}

构造函数

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //创建指定容量initialCapacity的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //创建空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     *默认构造函数,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,
     也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        //如果指定集合元素个数不为0
        if ((size = elementData.length) != 0) {
            // 考虑到返回的可能不是Object类型的数组,
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size,
                                                Object[].class);
        } else {
            // 用空数组代替
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

在第三个构造函数中,有一个这样的判断:

if (elementData.getClass() != Object[].class)

考虑到下面一种情况,若没有上面的判断,则会导致elementData实际类型是String[],而不是Object[],因此当你将其中一个元素更换为Object元素时会报错。

List<Object> l = new ArrayList<Object>(Arrays.asList("foo", "bar"));
l.set(0, new Object());

如果没有那句判断,相当于:

Object[] arr = new String[]{"a","b"};
arr[0]=new Object();

add方法

add方法在添加元素前,需要判断是否需要扩容

//将指定元素添加到list的末尾
public boolean add(E e) {
    //添加元素后可能导致容量不够,所以需要在添加之前进行判断是否扩容
    ensureCapacityInternal(size + 1);   
    elementData[size++] = e;
    return true;
}

ensureCapacityInternal方法

   //minCapacity是当前数组拟添加一个元素的长度,即size + 1
    private void ensureCapacityInternal(int minCapacity) {
    /*
        判断当前数组elementData是否为空元素数组(当使用无参构造方法
        时,elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA)。
        若是,则max(默认容量DEFAULT_CAPACITY,size + 1)。
        如果是第一次添加元素时,显然minCapacity为10
    */
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

ensureExplicitCapacity方法

private void ensureExplicitCapacity(int minCapacity) {
/*  问:modCount变量代表了什么?
    答:在使用迭代器遍历ArrayList里的数组时,modCount变量用于检查数组里
    的元素数量是否发生变化,这主要是在多线程环境中使用:防止一个线程迭代遍历
    的同时,另一个线程修改了这个列表的结构。如果modCount变量变化了,
    迭代器就抛出异常ConcurrentModificationException。
    可以参考如下代码,它会抛出异常。
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(10);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer==10)
                list.remove(integer);   //此处
        }
    当我们remove掉唯一的元素后,size变为了0,而此时Iterator的游标cursor是 1 ,
    在ArrayList迭代器的hasNext()方法中:
    public boolean hasNext() {
            return cursor != size();
    }
    cursor确实不等于size,因此还会进行下一次循环。如果我们不通过modCount和
    expectedModCount(创建迭代器的时候将当时的modCount赋值给expectedModCount)
    判断,则程序会报ArrayIndexOutOfBoundsException错误。JDK只抛出使用者造成
    的错误,因此定义了检查。
*/
    modCount++; 
    //如果最小容量超出了当前数组长度
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);//执行扩容的方法
}

grow方法

/*
    考虑到不同的JVM会加入一下数据头,当扩容后的容量大于MAX_ARRAY_SIZE,
    我们会去比较最小需要容量和MAX_ARRAY_SIZE。
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
    // oldCapacity为旧数组的容量
    int oldCapacity = elementData.length;
    // newCapacity为新数组的容量,它是旧容量的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 检查新容量的大小是否小于最小需要容量,若小于则将最小容量置为数组的新容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //如果新容量大于MAX_ARRAY_SIZE,使用hugeCapacity方法比较
    //最小容量和MAX_ARRAY_SIZE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 扩展新数组,然后将原数组中的元素拷贝
    elementData = Arrays.copyOf(elementData, newCapacity);
}

hugeCapacity方法

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) 
        throw new OutOfMemoryError();
    //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    //对minCapacity和MAX_ARRAY_SIZE进行比较:
    //若minCapacity大,将Integer.MAX_VALUE作为新数组大小
    //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组大小
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE 
                                    : MAX_ARRAY_SIZE;
}

假设当前正添加第二个元素:

(1)在ensureCapacityInternal方法中,调用方法ensureExplicitCapacity(2)

(2)在ensureExplicitCapacity方法中,执行ensureExplicitCapacity(2),此时该方法的唯一条件不满足,不会进入扩容方法grow。

(3)假设之后又添加了第3,4...10个元素,都不会执行扩容方法。

(4)当add第11个元素时,就会进入grow方法:计算出newCapacity = 15,后两个判断条件都不满足,因此数组扩容为15,size变为11.

add(int index,E element)方法

//在元素序列 index 位置处插入
public void add(int index, E element) {
    rangeCheckForAdd(index); //校验传递的index参数是不是合法
    // 检测是否需要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将 index 及其之后的所有元素都向后移一位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 将新元素插入至 index 处
    elementData[index] = element;
    size++;
}
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0) //判断index是否在[0, size - 1]区间中
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

remove方法

ArrayList支持两种删除元素的方式:

(1)remove(int index): 按照下标删除

public E remove(int index) {
    rangeCheck(index); //校验下标是否合法
    modCount++;//修改list结构,就需要更新这个值
    E oldValue = elementData(index); //根据下标查找值

    int numMoved = size - index - 1;//index后面有多少个元素
    if (numMoved > 0)
        //index后面的所有元素左移一位
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //移动后,原数组中size位置为null
    elementData[--size] = null; // clear to let GC do its work
    //返回旧值
    return oldValue;
}

(2)remove(Object o):按照元素删除,会删除和参数匹配的第一个元素

public boolean remove(Object o) {
    //如果元素是null 遍历数组移除第一个null
    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;
}
//根据下标来移除元素
private void fastRemove(int index) {
  modCount++;
  int numMoved = size - index - 1;//计算index后面有多少个元素
  if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,
                     numMoved);
  elementData[--size] = null; // clear to let GC do its work
}


参考资料

java ArrayList源码中注释的疑问

知乎问题:arraylist等记录修改次数modCount有什么作用?答主:wuxinliulei

ArrayList源码分析(扩容机制jdk8)

JavaGuide