ArrayList
- 底层是Object数组,是一个动态数组,其容量能自动增长。
- 是线程不安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。
- 查询速度快(底层是数组可按照索引获得元素),增删速度慢(添加、删除时该元素后面的所有元素都要移动,所以添加/删除数据效率不高)。
下面我们来分析ArrayList的源代码:
属性
transient Object[] elementData; //存储元素
private int size;//表示它包含的元素的数量。
ArrayList使用Object[] elementData来存储元素,size表示它包含的元素的数量。
无参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//默认初始容量
private static final int DEFAULT_CAPACITY = 10;
/**被用于默认大小的空实例的共享数组实例。
*其与EMPTY_ELEMENTDATA的区是:
*当我们向数组中添加第一个元素时,知道数组该扩充多少。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
通过上面源码我们可以知道elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA={};说明当我们调用无参构造时,底层数组容量为0,当我们第一次添加数据时才进行扩容,默认初始容量为10。
指定初始容量构造器
public ArrayList(int initialCapacity) {
//如果传入的参数大于0,就创建一个容量为传入的参数的Object数组赋值给elementData
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//如果传入的参数等于0,就将空的数组赋值给elementData
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
//抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
包含指定collection的构造器
public ArrayList(Collection<? extends E> c) {
//将Collection转化为数组赋给elementData。
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray可能(错误地)不返回Object[]
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 替换为空数组。
this.elementData = EMPTY_ELEMENTDATA;
}
}
分析源码我们可以知道,包含指定collection的构造器是将Collection转化为数组再赋给elementData。
下面我们来看一下它的扩容机制
当我们添加Collection时,我们可以看到它是遍历Collection,调用add(E e)方法。将集合的每个数据添加到ArrayList
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
//遍历Collection,将集合的每个数据添加到ArrayList
for (E e : c)
if (add(e))
modified = true;
return modified;
}
当我们添加单个数据时,我们可以看到add(E e)方法中调用的ensureCapacityInternal((size + 1);来确定容量。
public boolean add(E e) {
//确定容量
ensureCapacityInternal(size + 1); // Increments modCount!!
//size++自增,并将添加的元素赋值给数组下标为size++的对象
elementData[size++] = e;
return true;
}
ensureCapacityInternal(int minCapacity)方法,使minCapacity等于默认容量10和传入的容量中的较大值。然后调用了ensureExplicitCapacity(minCapacity);方法,判断是否扩容。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//判断是否扩容
ensureExplicitCapacity(minCapacity);
}
当传入的容量大于原来的数组容量时,调用grow(minCapacity);进行扩容。
private void ensureExplicitCapacity(int minCapacity) {
//modCount表示这个列表的结构被修改的次数。
modCount++;
// 当传入的容量大于原来的数组容量时,进行扩容
if (minCapacity - elementData.length > 0)
grow(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)
//如果传入的容量大于能分配的数组的最大大小,则使新的容量==Integer.MAX_VALUE :
//如果传入的容量小于能分配的数组的最大大小,则使新的容量==能分配的数组的最大大小
newCapacity = hugeCapacity(minCapacity);
//将原来的数组拷贝到新的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
扩容为原来的数组容量的1.5倍,若扩容后的容量还是不够,直接使容量变为你所需要的容量。然后将原来数组中的数据拷贝到新的数组中。