实现的接口
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}
ArrayList实现了三个接口,这三个接口都是标志性接口(接口中没有任何方法)。
RandomAccess:
表示可以随机访问,Collections工具类中许多通用的算法,它们就会通过判断是否实现了该接口来选择不同的实现方式。
Cloneable:
表示可克隆的,只有实现了该接口才可以调用Object类中的clone()方法,ArrayList对clone方法进行了重写,但是它仍然需要调用Object中的方法clone()。
Serializable
:表示可序列化的,只有实现了该接口的类,它的对象才能被序列化(通过ObjectOutputStream将对象写入文件)
成员变量
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; /** * 大小为0的数组,当调用有参构造创建初始化容量为0的ArrayList时赋值给elementData */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 大小为0的数组,当调用无参构造创建ArrayList时赋值给elementData * 该数组区别于EMPTY_ELEMENTDATA是为了在第一次添加元素时判断如何扩容 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 用于存放数据元素的数组,Object类型数组,可以接收任何类型的数据 * 该数组没有使用private修饰,是为了方便后面迭代器内部类访问 */ transient Object[] elementData; /** * 已存放元素的个数 */ private int size; }
从成员变量的信息我们主要可以得出:ArrayList是默认初始化容量为10的Object类型数组。
构造方法
ArrayList提供了三个构造方法,分别无参构造,指定初始化容量的构造方法,通过一个集合来构造ArrayList的构造方法。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { /** * 指定初始化容量的构造方法 */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { //当参数大于0的时候,直接创建初始化容量为initialCapacity的数组 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //将参数等于0的数组赋值给elementData this.elementData = EMPTY_ELEMENTDATA; } else { //参数为负数时 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * 无参构造 */ public ArrayList() { //默认初始化容量为10,但是在添加元素之前容量一直为0 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 通过一个集合来构造ArrayList,元素的存放顺序为传入集合迭代器返回的顺序 */ public ArrayList(Collection<? extends E> c) { //将集合转换为Object数组 elementData = c.toArray(); if ((size = elementData.length) != 0) { //JDK老版本的bug,意思就是c.toArray()返回的类型可能不是Object类型,现在已经被修复 //(see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 传入的集合为空集合 this.elementData = EMPTY_ELEMENTDATA; } } }
从这三个构造方法我们可以知道:调用无参构造创建出来的ArrayList在第一次添加元素之前容量一直为0;我们也知道了EMPTY_ELEMENTDATA
、DEFAULTCAPACITY_EMPTY_ELEMENTDATA
这两个大小为0的数组什么时候用来赋值。
涉及数组容量的方法
copyOf和arrayCopy
ArrayList在扩容时会调用的copyOf方法,copyOf方法又是使用arrayCopy方法实现的。
我们这里先说arrayCopy方法,arrayCopy底层是使用C语言实现的,实现的功能是将一个数组中的内容拷贝至另外一个数组(浅拷贝)。
copyOf实现功能:通过截断或者填充将原数组转换为指定长度的新数组。
下面我们看一下copyOf的源码,如下:
public static <T> T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); } public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @SuppressWarnings("unchecked") //判断newType是否是Object类型 T[] copy = ((Object)newType == (Object)Object[].class) //是Object类型,直接创建长度为newLength的Obejct类型数组 ? (T[]) new Object[newLength] //创建非Object类型数组 : (T[]) Array.newInstance(newType.getComponentType(), newLength); //将原数组内容拷贝至新创建的数组,拷贝长度为原先数组长度和新长度中较小的一个 System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); //将新数组返回 return copy; }
数组扩容的方法
public void ensureCapacity(int minCapacity) { //排除通过无参构造创建的ArrayList,该ArrayList还未添加元素(为扩容),并且minCapacity小于10这种特殊情况 if (minCapacity > elementData.length && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA && minCapacity <= DEFAULT_CAPACITY)) { modCount++; grow(minCapacity); } } private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private Object[] grow(int minCapacity) { return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity)); } private Object[] grow() { return grow(size + 1); }
获取扩容后的新容量
private int newCapacity(int minCapacity) { int oldCapacity = elementData.length; //新容量为旧容量的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); //新容量大于需要的最小容量 if (newCapacity - minCapacity <= 0) { //使用无参构造创建的数组,此时还未添加元素,数组容量为0 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) return Math.max(DEFAULT_CAPACITY, minCapacity); //minCapacity太大,超出了整型范围 if (minCapacity < 0) throw new OutOfMemoryError(); return minCapacity; } //返回新容量 //所需最小容量超出了数组的最大容量,返回Integer.MAX_VALUE //所需容量小于数组最大容量,返回新容量 return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
可能光看上面方法对扩容机制感到十分混乱,下面进行梳理以下:
从扩容的原因分析,有以下两种情况:
- 调用add方法,数组容量不足,需要扩容。
1)使用无参构造创建的ArrayList,并且是第一次添加元素,扩容后的新容量为10
.
2) 在添加元素时容量不足,扩容后的新容量为原先容量的1.5倍
(除过初始容量为1的ArrayList第一次扩容,它第一次扩容后的容量为2)。
3)原先容量的1.5倍大于MAX_ARRAY_SIZE
,并且小于Integer.MAX_VALUE
,扩容后容量为Integer.MAX_VALUE
- 调用ensureCapacity方法,来保证数组所需的最小容量。
1)minCapacity小于原先容量的1.5倍,扩容后的新容量为原先容量的1.5倍
。
2)minCapacity大于原先容量的1.5倍,扩容后的新容量为minCapacity
。
ArrayList中的Iterator
public Iterator<E> iterator() { return new Itr(); } private class Itr implements Iterator<E> { // 下一个要返回元素的下标,默认为0 int cursor; // 最后一次返回元素的下标 int lastRet = -1; //ArrayList预期的修改次数,在获取迭代器之后若再通过非迭代器方法对ArrayList进行增删改操作, //修改次数的预期值与实际值就会不同,迭代器不能在继续使用 int expectedModCount = modCount; Itr() {} //判断是否还有可获取的元素 public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") 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 = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); //将cursor往前挪了一位 cursor = lastRet; //lastRet被重置为-1,这也解释了remove为什么不能连续使用两次 lastRet = -1; //修改预期修改次数为当前的实际修改次数 expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } //对未遍历的全部元素进行指定操作 @Override public void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); final int size = ArrayList.this.size; int i = cursor; if (i < size) { final Object[] es = elementData; if (i >= es.length) throw new ConcurrentModificationException(); for (; i < size && modCount == expectedModCount; i++) action.accept(elementAt(es, i)); //将cursor一次更新到最后一次操作的下一个位置,lastRet移动到最后一次操作的位置 cursor = i; lastRet = i - 1; checkForComodification(); } } //检查expectedModCount是否与modcount相同,若不同抛出ConcurrentModificationException final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
通过ArrayList中获取迭代器源码源码的学习,我们可以知道:
- ArrayList的迭代器维护了一个用来遍历集合的下标,next()方法就是通过该下标来获取元素的,每使用一次next,该下标就会往后移一位。
- ArrayList和它的迭代器都维护了一个集合的修改次数,迭代器的next(),remove()方法在执行之前都会检查两个修改次数书是一致,若不一致就会抛出异常。
- ArrayList的迭代器维护了一个上一次返回元素的下标,初始值为-1,在使用next方法后,该下标会被更新,remove方法就是以该下标为参数调用ArrayList的remove方法。
- 在使用一次迭代器的remove方法后,迭代器维护的上一次返回元素的下标会重新置为-1.这就解释了为什么remove()方法不能连续使用。
- 迭代器的remove方法也是通过调用集合的remove方法来删除上一次遍历的元素,所以集合维护的修改次数在调用remove方法之后也会发生改变,为保证不影响迭代器的继续使用,迭代器的remove方法是会更新迭代器维护的集合修改次数的。