概述 ArrayList 是实现 List 接口的动态数组,所谓动态就是它的大小是可变的。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。
注意,ArrayList 实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。所以为了保证同步,最好的办法是在创建时完成,以防止意外对列表进行不同步的访问: List list = Collections.synchronizedList(new ArrayList(…));

几个参数
//ArrayList的默认容量为10private static final int DEFAULT_CAPACITY = 10;private static final Object[] EMPTY_ELEMENTDATA = {};private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//一个对象数组,用于存放数据transient Object[] elementData;//用于表示当前数组内有多少条数据private int size;//数组的最大容量private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
- DEFAULT_CAPACITY 每个ArrayList容器都有一个容量,这个参数就是默认的初始容量大小
- elementData 这是一个对象数组,从此我们可以看到ArrayList的底层用一个对象数组来存储数据的
- size 用来表示当前容器里已有多少条数据
- modCount 继承自父类AbstractList,用于记录容器修改的次数(增,删)
- MAX_ARRAY_SIZE 数组的最大容量是Integer.MAX_VALUE - 8,对于空出的8位,有以下三个解释:①需要8位来存储ArrayList自己的size,以避免机器内存的溢出②最大还是能支持到 Integer.MAX_VALUE(当Integer.MAX_VALUE-8依旧无法满足需求时)
构造器
ArrayList提供了三个构造器:
- ArrayList():默认构造函数,提供一个默认空列表。
- ArrayList(int initialCapacity):构造一个具有指定初始容量的空列表。
- ArrayList(Collection<? extends E> c):构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
/***默认构造器,使用定义的默认空的Object数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA*/public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}/***构造一个具有指定初始容量的空列表*@param initialCapacity 初始容量*/public ArrayList(int initialCapacity) {//如果指定的初始容量大于0(即合法)if (initialCapacity > 0) {//构造一个新的指定容量的Object数组this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {//如果指定的容量为0,则使用定义的空Object数组EMPTY_ELEMENTDATAthis.elementData = EMPTY_ELEMENTDATA;} else {//输入容量不合法,抛出异常throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);}}/***构造一个包含指定Collection集合元素的列表*@param c 指定的Collection集合类*/public ArrayList(Collection<? extends E> c) {//将集合转换为数组保存elementData = c.toArray();//保存该数组的长度(原集合的size),如果不是空集合,进行数组拷贝if ((size = elementData.length) != 0) {if (elementData.getClass() != Object[].class)elementData = Arrays.copyOf(elementData, size, Object[].class);} else {//如果是空集合,则使用空的Object数组EMPTY_ELEMENTDATAthis.elementData = EMPTY_ELEMENTDATA;}}
添加元素
ArrayList里有4个用于向容器里添加元素的方法
public boolean add(E e); //向容器的末尾添加元素
public void add(int index, E element); //向指定的位置插入元素,后面的元素向后移
public boolean addAll(Collection<? extends E> c); //向容器的末尾添加集合c里所有的元素
public boolean addAll(int index, Collection<? extends E> c); //从指定位置开始向容器里添加集合c里所有的元素
public void set(E e);
/***向容器的末尾添加元素*@param e 待插入的元素*/public boolean add(E e) {//用于扩容的方法ensureCapacityInternal(size + 1);//向数组末尾插入元素,size+1elementData[size++] = e;return true;}
在这里ensureCapacityInternal(size + 1); 方法主要是用于判断是否需要扩容,传入数组所需的最小容量minCapacity,再进行插入元素操作。
/***向容器内指定位置插入元素*@param index 指定数组下标*@param element 待插入的元素*/public void add(int index, E element) {//进行数组下标的范围检查,是否越界rangeCheckForAdd(index);//同样的进行扩容检查ensureCapacityInternal(size + 1);//将指定下标后的元素均向后移一位System.arraycopy(elementData, index, elementData, index + 1,size - index);//向数组内指定位置插入元素elementData[index] = element;//size+1size++;}
在这里rangeCheckForAdd(index); 方法用于检测输入的下标是否越界,越界则抛异常。同样也有扩容检查方法。
从System.arraycopy(elementData, index, elementData, index + 1, size - index); 方法我们可以看出,该方法是用于把一个数组中某一段字节数据放到另一个数组中。因此add方法向指定下标里添加元素并不是覆盖原元素,而是将后面元素向后移空着该位置用于插入元素。
/***向容器的末尾添加集合c里所有的元素*@param c 待插入元素的集合*/public boolean addAll(Collection<? extends E> c) {//获取集合元素的数组Object[] a = c.toArray();int numNew = a.length;//判断是否扩容ensureCapacityInternal(size + numNew);//插入数组元素System.arraycopy(a, 0, elementData, size, numNew);size += numNew;//只要集合不为空,则返回truereturn numNew != 0;}/***向容器的指定位置添加集合c里所有的元素,同理*@param index 待插入数组的位置*@param c 待插入元素的集合*/public boolean addAll(int index, Collection<? extends E> c) {rangeCheckForAdd(index);Object[] a = c.toArray();int numNew = a.length;ensureCapacityInternal(size + numNew); // Increments modCountint 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;}
扩容机制
从上面的添加元素方法里我们都会看到有扩容的方法,目的是为了让数组始终可以装得下所需要的元素。这里讨论一下ArrayList的扩容机制,主要有以下几个方法:
private void ensureCapacityInternal(int minCapacity);
private void ensureExplicitCapacity(int minCapacity);
private void grow(int minCapacity);
/***用于确认内部容量*@param minCapacity 所需最小容量*/private void ensureCapacityInternal(int minCapacity) {//如果容器是默认空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则取所需最小容量和默认容量两者最大值if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity);}/***判断是否需要扩容*@param minCapacity 所需最小容量*/private void ensureExplicitCapacity(int minCapacity) {modCount++;// 如果所需最小容量 大于 当前数组长度,则表明需要扩容if (minCapacity - elementData.length > 0)//进行扩容grow(minCapacity);}/***扩容方法*@param minCapacity 所需最小容量*/private void grow(int minCapacity) {//记录旧容量值,即现数组长度int oldCapacity = elementData.length;//计算新容量值,右移1位即乘2,newCapacity = oldCapacity * 1.5int newCapacity = oldCapacity + (oldCapacity >> 1);//判断,假如新容量比所需最小容量小,则直接将最小容量当作新容量if (newCapacity - minCapacity < 0)newCapacity = minCapacity;//如果新容量比数组最大大小大,则调用hugeCapacity()方法计算新容量if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);//数组复制扩容elementData = Arrays.copyOf(elementData, newCapacity);}/***计算新容量*@param minCapacity 所需最小容量*/private static int hugeCapacity(int minCapacity) {//判断数值是否合法,溢出抛错误if (minCapacity < 0)throw new OutOfMemoryError();//若所需最小容量大于数组最大长度,返回Integer.MAX_VALUE,否则直接给数组最大长度//MAX_VALUE = 0x7fffffff//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;}
删除元素
ArrayList里的删除元素主要有下面3个方法:
public E remove(int index);
public boolean remove(Object o);
public boolean removeAll(Collection<?> c);
/***删除指定下标的元素*@param index 待删除元素的下标*/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);//数组待删除元素置为null,size-1,交给GC自动垃圾回收elementData[--size] = null;return oldValue;}/***移除此列表中首次出现的指定元素(如果存在)*@param 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;}/***私有的快速删除方法*@param index 待删除元素的下标*/private void fastRemove(int index) {modCount++;//计算删除后所需移动的元素个数int numMoved = size - index - 1;if (numMoved > 0)//数组移动System.arraycopy(elementData, index+1, elementData, index,numMoved);//数组待删除元素置为null,size-1,交给GC自动垃圾回收elementData[--size] = null;}/***删除集合内所有匹配的元素*@param c 待删除元素集合*/public boolean removeAll(Collection<?> c) {//判断c集合是否为空Objects.requireNonNull(c);//调用batchRemove()方法return batchRemove(c, false);}/***删除集合内所有匹配的元素*@param c 待删除元素集合*@param complement 为false时,删除集合内匹配元素,true时,保存匹配元素*/private boolean batchRemove(Collection<?> c, boolean complement) {//获取当前元素数组final Object[] elementData = this.elementData;int r = 0, w = 0;//标志域boolean modified = false;try {//遍历数组内所有元素,判断集合内是否有相同元素for (; r < size; r++)//当complement为false时,表示把数组内不包含集合内元素的元素覆盖到原数组里,顺便前移if (c.contains(elementData[r]) == complement)elementData[w++] = elementData[r];} finally {//当遍历完时,r一定会等于size,这一步判断主要是为了避免一些意外情况//1.上面的for循环抛出了异常//2.并发问题,其他的线程添加了元素if (r != size) {//进行数组调整,复原System.arraycopy(elementData, r,elementData, w,size - r);w += size - r;}//如果w != size,意思是上面的循环里存在重复元素被删除了if (w != size) {//因为以及将元素前移了(替换了),所以将数组后面的元素循环置null,交给GC垃圾回收for (int i = w; i < size; i++)elementData[i] = null;//记录集合修改次数modCount += size - w;size = w;modified = true;}}return modified;}/***删除指定下标范围内的元素*@param fromIndex 起始下标*@param toIndex 结束下标*/protected void removeRange(int fromIndex, int toIndex) {modCount++;int numMoved = size - toIndex;System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);// clear to let GC do its workint newSize = size - (toIndex-fromIndex);for (int i = newSize; i < size; i++) {elementData[i] = null;}size = newSize;}
优点
- ArrayList底层是基于动态数组实现的,对于随机访问的get和set方法,ArrayList的性能很高
- 对于直接向尾部添加元素的add()方法,ArrayList的性能也是非常高的
缺点
- 对于向容器中间添加和删除元素操作,ArrayList的需要对数组元素进行移动,调用System.arraycopy()方法对数组进行复制,这会大大降低性能
- ArrayList是非线程安全的
- ArrayList会有预留的空余内存空间,会造成内存空间浪费
