我们在存储多个元素时,如果元素个数固定,那么可以使用数组即可,但是在需要存储的元素个数不确定时,这时候数组可以出现数据下标越界的情况,数组对于动态元素添加显得无能为力,而ArrayList可以根据实际元素个数的变化而动态扩容,弥补了数组的局限性
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList具备List的特性,同时支持随机访问,原生支持序列化
//默认容量
private static final int DEFAULT_CAPACITY = 10;
//空数组,当初始化指定容量为0或者指定初始化元素个数为0时使用
private static final Object[] EMPTY_ELEMENTDATA = {};
//空数组 区别于上面空数组,仅在无参数构造器时使用,表示默认值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//用于存储数据数组
transient Object[] elementData; // non-private to simplify nested class access
//该ArrayList中的元素个数
private int size;
ArrayList具有三个构造方法,对应不同初始化
public ArrayList() {
//直接将默认空数组赋值给存储元素的数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//当指定参数大于0,则直接new对应大小的元素素组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//当指定参数等于0,赋值为空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//如果参数小于0,则抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
public ArrayList(Collection<? extends E> c) {
//首先将集合转为数组
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//如果集合元素不为空并且不为Object数组,则将数组修正为Object数组
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//如果集合元素为空,则直接初始化空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList最大的特点就是可以动态扩容,下面我们分析扩容过程
//确认容量是否需要扩容
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果为默认初始化,则将最小所需容量设置为默认大小10和传入参数(希望最小容量)的最大值
//在第一次添加元素时,数组容量默认值为10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//确认实际需要的容量大小
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果最小希望容量大于当前存储元素数组长度,则进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//扩容
private void grow(int minCapacity) {
//记录原存储元素数组长度
int oldCapacity = elementData.length;
//每次扩容基于原存储元素数组长度 * 1.5 (即原来容量的1.5倍)
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
//如果扩容后还是小于最小所需容量,则将最小所需容量设置为新容量值
newCapacity = minCapacity;
//这里的MAX_ARRAY_SIZE为 Integer.MAX_VALUE - 8,如果分配更大的值,可能会造成OutOfMemoryError,但是ArrayList支持将容量扩大到Integer.MAX_VALUE
if (newCapacity - MAX_ARRAY_SIZE > 0)
//如果大于ArrayList所希望的最大值,继续扩容
newCapacity = hugeCapacity(minCapacity);
//将原存储元素数组中的所有元素拷贝之新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
//这里minCapacity小于零是不可能的,只有在该值已经溢出Integer的值才会出现小于零的情况,抛出异常
if (minCapacity < 0)
throw new OutOfMemoryError();
//分配容量最大值为Integer.MAX_VALUE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
扩容过程主要分为3步:
ArrayList每次扩容就是新建一个存储元素数组,数组大小为原数组1.5倍,然后将原数组元素拷贝至新数组
顺序添加元素 add(E e) 方法
public boolean add(E e) {
//确定容量
ensureCapacityInternal(size + 1);
//将元素添加至现有元素的末尾
elementData[size++] = e;
return true;
}
只要清楚ArrayList的扩容过程,添加元素的过程就很简单了,只需要将元素添加到现有元素的末尾
指定位置添加元素 add(E e)
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++;
}
删除元素 remove() 方法
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;
}
优点:ArrayList基于数组实现,在随机访问上又很大的优势,可以通过数组的下标直接访问对应的数据,对于修改数据同样也很高效,可以通过指定位置取出元素进行修改或替换,动态扩容的机制很好适应使用环境的变化
缺点:ArrayList对于删除和随机插入的性能并是很好,只要不是添加或者删除末尾元素就会涉及到位置整理的过程,如果对于需要存入大量数据,并且不指定初始化容量,会导致频繁的扩容,将会影响整体业务的性能
ArrayList对于顺序添加和删除的场景是非常适合的,具备数组的高效的同时也兼备了动态的特性,由于扩容过程中的拷贝会损耗性能,在使用时可以估算需要的大小,在初始化时ArrayList使用带容量参数的构造器指定初始化容量,尽量避免触发扩容,提升性能。
(转载请注明出处)