1. ArrayList简介
ArrayList 是List接口的大小可变数组的实现,利用grow方法实现自动扩容;
ArrayList 本质上是一个Object数组,允许存储null值;
ArrayList 实现了RandomAccess,所以在遍历它的时候推荐使用for循环,其iterator和listIterator方法返回的迭代器是快速失败的;
ArrayList 查询性能高于LinkedList,而插入删除性能不如LinkedList;
2. ArrayList 继承关系
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
3. ArrayList 数据结构
// 序列化id
private static final long serialVersionUID = 8683452581122892189L;
// 默认的初始化容量
private static final int DEFAULT_CAPACITY = 10;
//指定该ArrayList容量为0时,返回该空数组。
private static final Object[] EMPTY_ELEMENTDATA = {};
//当调用无参构造方法,返回的是该数组。刚创建一个ArrayList 时,其内数据量为0。与EMPTY_ELEMENTDATA的区别就是:该数组是默认返回的,而EMPTY_ELEMENTDATA是在用户指定容量为0时返回。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//保存添加到ArrayList中的元素。
//该值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时,当第一次添加元素进入ArrayList中时,数组将扩容值DEFAULT_CAPACITY。
//被标记为transient,在对象被序列化的时候不会被序列化。
transient Object[] elementData;
//ArrayList的实际大小,默认为0
private int size;
//分派给arrays的最大容量
//减8是因为某些VM会在数组中保留一些头字,尝试分配这个最大存储容量可能会导致array容量大于VM的limit,最终导致OutOfMemoryError。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
elementData是由transient修饰的,表示序列化时忽略该属性,但是我们知道ArrayList肯定是可以进行序列化的,这是由于ArrayList的writeObject和readObject方法
add方法过程:
- 检查ArrayList的容量,来决定是否扩容;
- 在elementData[size++]处赋值
calculateCapacity 主要应用于无参构造时;
ensureExplicitCapacity 判断最小容量是否大于数组缓冲区当前长度,若大于则扩容;
//将指定的元素追加到此列表的末尾
public boolean add(E e) {
//确认list容量,如果不够,容量加1
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) {
// 取minCapacity为DEFAULT_CAPACITY和参数minCapacity之间的最大值。DEFAULT_CAPACITY默认是10。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//数组容量检查,不够时则进行扩容
// minCapacity 想要的最小容量
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//最小容量>数组缓冲区当前长度
if (minCapacity - elementData.length > 0)
grow(minCapacity);//扩容
}
grow方法过程:
- 确定新容量newCapacity;
- 利用Arrays.copyOf(elementData, newCapacity)方法改变容量大小;
//扩容,保证ArrayList至少能存储minCapacity个元素
//第一次扩容,在原有的容量基础上增加一半,如果容量还是小于 minCapacity,就将容量扩充为 minCapacity。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 当前容量增加1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果扩容后的容量还是小于想要的最小容量
if (newCapacity - minCapacity < 0)
//将扩容后的容量再次扩容为想要的最小容量
newCapacity = minCapacity;
//如果扩容后的容量大于临界值,则进行大容量分配
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//新的容量大小已经确定好了,就copy数组,改变容量大小。
elementData = Arrays.copyOf(elementData, newCapacity);
}
//进行大容量分配
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) throw new OutOfMemoryError();
//如果想要的容量大于MAX_ARRAY_SIZE,则分配Integer.MAX_VALUE,否则分配MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
4. 总结:
ArrayList 是利用Object数组实现容量大小动态可变,扩容机制为首先扩容为原始容量的 1.5 倍。如果1.5倍太小的话,则将我们所需的容量大小赋值给 newCapacity,如果1.5倍太大或者我们需要的容量太大,那就直接拿 newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE 来扩容。 扩容是通过数组的拷贝,所以尽可能减少扩容操作。