ArrayList详解(基于JDK1.8)

281 阅读3分钟

1.ArrayList继承关系如下:

RandomAccess接口:是一个标志接口,表明实现了这个接口的List集合支持快速随机访问。如果是实现了这个接口的List集合,使用for循环方式获取数据比使用迭代器方式更快。

2.主要的成员变量

//实际元素个数

private int size;

//一个Object对象数组

transient Object[] elementData;

//数组的默认初始化容量10,无参构造函数的初始化容量

private static final int DEFAULT_CAPACITY = 10;

//两个空的Object数组

private static final Object[] EMPTY_ELEMENTDATA = {};

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

这里使用两个空的数组是用来区分ArrayList是由有参构造函数构造的还是无参构造函数构造的。以便在第一次向集合add元素的时候,知道对应的扩容方案。

//一个记录对集合的操作次数的整型变量

protected transient int modCount = 0;

3.主要方法解析

(1).add方法

在调用add方法向集合中添加元素时,会先调用ensureCapacityInternal()方法保证数组的容量,参数minCapacity所需要的的最小容量为当前元素的实际个数size + 1,让我们看看ensureCapacityInternal()这个方法。

先调用calculateCapacity()这个静态方法确认所需要的最小容量minCapacity。

这里之所以要确认一下最小容量,主要是当使用无参构造函数来创建集合时,数组的默认容量为10,第一次向集合中添加元素时会直接把数组的容量扩为10,这里的minCapacity赋值为10,有参构造函数第一次添加元素,minCapacity仍然为size + 1。

然后调用ensureExplicitCapacity()方法。

判断所需要的最小容量是否大于当前数组的长度,如果大于就调用grow()方法进行扩容。

旧容量oldCapacity为当前数组的长度,新容量

**                            newCapacity = oldCapacity + oldCapacity/2**

即数组容量扩容为原来的1.5倍(向下取整)。在newCapacity和minCapacity中取最大值作为数组的最终扩容容量赋值给newCapacity。然后判断最终扩容容量是否大于集合所定义的最大容量,如果大于调用静态方法hugeCapacity()。

判断之前所需的最小容量minCapacity是否大于集合定义的最大容量(整型最大值 - 8),如果大于newCapacity赋值为整型最大值,反之为集合定义最大容量。

然后调用Arrays.copyOf()进行扩容操作。

这个方法的底层是调用的一个native方法System.arraycopy()最终完成的数组拷贝操作。

4.关于循环地向list中remove元素

(1).普通for循环

使用普通for循环循环地remove元素时,要注意集合的实际容量一直在变化,你的索引也一直在变化,在遍历的时候会漏掉一些元素。

(2).增强for循环

本质上就是通过迭代器实现的,与下面的代码等效。

在增强for循环中循环地进行remove(或者add)操作,会抛ConcurrentModificationException异常,因为remove操作和add操作均会修改modCount的值,在迭代器的next方法中,每次都会调用checkForComodification()方法来校验当前modCount值与期望值是否相同,因此会抛异常。

(3).迭代器

使用迭代器可以做正常循环并删除,但是必须要使用迭代器的remove方法,不然还是会抛ConcurrentModificationException异常。