JDK集合框架--ArrayList
ArrayList,从类名就可以看出来,这是由数组实现的List,即内部是用数组保存元素的有序集合。先看看主要的成员变量,比较简单:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { /** * 默认的初始数组大小 */ private static final int DEFAULT_CAPACITY = 10;/** *用于储存具体元素的数组对象 */ transient Object[] elementData; // non-private to simplify nested class access /** * *数组中元素的数量 */ private int size;
三个构造函数:
/** * * 创建一个确定初始大小的数组 * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * 不创建数组,使用默认的空数组 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** *传入一个Collection 的实现类 并转换成Object[]数组*/ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652)->有些集合的toArray方法返回的对象类型可能不是Object[].class if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 使用空数组作为初始数组 this.elementData = EMPTY_ELEMENTDATA; } }
如果可以大体上确定ArrayList需要所元素的数量,尽量使用ArrayList(int initialCapacity)指定初始容量,可以有效避免其扩容次数(多数底层使用数组实现的集合有相同的特点)
接下来分析其扩容机制,先看看add(E e)方法:
public boolean add(E e) { ensureCapacityInternal(size + 1); //判断是否需要扩容 elementData[size++] = e;//添加对象引用 return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { //即使用的是无参构造器创建对象,且是第一次添加元素的情况 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //如果当前所需最小容量小于默认值10,则最小值作为当前容量 return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { modCount++; //如果当前所需的容量超出,之前数组大小 则进行扩容操作 if (minCapacity - elementData.length > 0) grow(minCapacity); } //核心扩容方法 private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1);//原来数组的1.5倍 //新数组的大小必须至少为原数组大小的1.5倍,否则直接扩容为1.5倍 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //新的数组长度如大于默认最大的数组长度MAX_ARRAY_SIZE if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 创建数组,将旧数组中的数据复制到新数组中 elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //所需最小的数组大小如果大于MAX_ARRAY_SIZE,则直接取int类型最大值,反之,取默认最大值MAX_ARRAY_SIZE return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
所有的添加操作都是需要调用ensureCapacityInternal方法,判断是否需要,并进行扩容操作
这里总结一下:
ArrayList默认容量是10,如果初始化时一开始指定了容量,或者通过集合作为元素,则容量为指定的大小或参数集合的大小。每次扩容为原来的1.5倍,如果新增后超过这个容量,则容量为新增后所需的最小容量。如果增加0.5倍后的新容量超过限制的容量,则用所需的最小容量与限制的容量进行判断,超过则指定为Integer的最大值,否则指定为限制容量大小。然后通过数组的复制将原数据复制到一个更大(新的容量大小)的数组。
再看看ArrayList中其他动态的方法:
//根据索引移除 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); elementData[--size] = null; // clear to let GC do its work return oldValue; } //根据具体的对象移除 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; } //移除所有元素 public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
所有的移除工作都是找到对应的数组下标项,然后置空,具体的对象回收是jvm的工作;进行删除和插入操作时,为了维持数组中元素的连续性,则需要调用System.arraycopy(),重新并排数组元素,时间复杂度是线性的,效率很低,所以ArrayList不适合需要经常对集合进行动态操作的场景。
最后看看ArrayList访问元素的方法:
public E get(int index) { rangeCheck(index); return elementData(index); }
很简单,就是利用数组的索引访问,常数时间复杂度,效率很高,所以对于频繁读取的场景,使用ArrayList再好不过了