首先让我们看下ArrayList的类图吧。
从类图中可以看到ArrayList类继承AbstractList类并实现了List接口,同时实现Cloneable和Serializable接口使得ArrayLis可克隆并且可序列化。
transient Object[] elementData;
elementData用于存储我们添加到ArrayList中的元素。从这里可以确定ArrayList确实是名副其实的由数组实现。
private static final int DEFAULT_CAPACITY = 10;
数组默认大小。如果调用无参构造方法的话就会将elementData的长度初始化为10。
private static final Object[] EMPTY_ELEMENTDATA = {};
空对象。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
空对象。如果调用无参构造方法则会将elementData初始化为该值。
private int size;
现在保存的元素数量。
protected transient int modCount;
用于记录list被更改的次数。由于ArrayList是非线程安全的,所以此值也用于检测是否出现了多线程问题。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
可保存的最大元素数量。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
无参构造方法,将elementData初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
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);
}
}
定长构造方法,将elementData初始化为EMPTY_ELEMENTDATA并进行输入检查。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//将elementData指向collection转换后数组
if ((size = elementData.length) != 0) {
//如果数组不为空,执行copy方法将collection中元素复制到elementData
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//Collection为空直接指向空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
有参构造方法,根据给定的Collection初始化为ArrayList。
add
方法public boolean add(E e) {
//进行elementData大小检查,确认是否需要进行扩容
ensureCapacityInternal(size + 1);
//扩容检查完成后将元素存储到对应位置。
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果elementData为空数组,则最小容量为默认容量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/2
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
//进行大小检查,如果新长度仍然小于所需最小长度,则直接使用最小长度作为初始化大小
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
//进行最大值检查,仅在未超过最大长度时进行扩容。
newCapacity = hugeCapacity(minCapacity);
// 将elementData指向复制后的新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
//最大值计算,可存储最大值为Integer.MAX_VALUE,即2147483647
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
//说明需要存储的数量已经超过Integer.MAX_VALUE,直接抛异常
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
在这里总结下add
方法的流程:
modCount
自增并进行扩容检查。elementData
则需要进行扩容,默认将容量扩充为原来的1.5倍。size++
位置。从这里可以确认的是,如果在初始化时不给出确定的大小的话,在存储元素不断增长时是会进行多次扩容操作的,这个操作相比较而言还是很耗费时间的。所以还是建议在初始化时给出一个默认容量。
get
方法相比较来说,get方法就比较简答了。先检查输入index是否越界,不越界则直接返回对应元素。
public E get(int index) {
//边界检查
rangeCheck(index);
//根据索引返回所需元素
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}