特点
随机访问
:按索引访问只需要O(1)的时间,访问第i个元素的地址=起始地址+单位长度*i读快写慢
:插入元素需要O(n)的时间- 允许插入
null
元素 - 使用索引迭代比使用Iterator迭代
快
非线程安全
成员变量和常量
- private
transient
Object[]elementData
使用transient关键字标记,表明这个对象不会被自动序列化,因为elementData是一个缓冲区,真正的数据部分只占其中的一部分,不需要对其全部进行序列化,java在writeObject
方法中,手动控制了只序列化elementData中有数据的部分。 - private int
size
真正的数据部分的大小 - protected transient int
modCount
modCount变量用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除
至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
在进行读写(序列化或遍历)操作时,需要比较操作前后modCount是否改变,如果改变了,就会抛出ConcurrentModificationException
异常。因为在进行读写操作时,如果改变了结构,那么这次操作的结果就是不正确的,需要抛出异常,即快速失败(Fail-Fast)机制。 - private static final int
MAX_ARRAY_SIZE
=Integer.MAX_VALUE-8
默认数组长度的上限,实际上数组长度的上限可达到Integer.MAX_VALUE
构造方法
- public ArrayList()
不传参数,elementData默认设置为10
- public ArrayList(int initialCapacity)
指定默认容量,即elementData的长度 - public ArrayList(Collection<? extends E> e)
从已有的集合创建,先设置elementData的长度为集合的大小,然后将集合的元素复制到elementData
方法
- 读get(index),写set(index,value),时间
O(1)
- 添加add/addAll
如果向末尾添加需要O(1)
时间,如果向中间插入需要O(n)
时间将后面的元素后移 - 删除remove/removeAll/retainAll
remove中间元素需要O(n)
的时间将后面的元素前移。
removeAll(Collection c)在list中删除c中存在的元素
retainAll(Collection c)在list中保留c中存在的元素,其他删除
这两个方法调用了batchRemove(Collection,Boolean)
方法,该方法的时间复杂度为O(m*n)
,因此应该谨慎使用这两个方法,可以使用hash表等空间获取时间的优化。 - 查找indexOf/lastIndexOf/contains
使用顺序查找
指定对象的索引,需要O(n)时间,contains直接调用了indexOf
扩容(重点)
- 在调用
add/addAll
方法时,需要调用ensureCapacityInternal(minCapacity)
方法确保elementData的长度足够容纳新元素,minCapacity
表示本次添加操作完成后需要的elementData的最小容量。 - 在
ensureCapacityInternal(minCapacity)
方法中,如果elementData为空,那么minCapacity
被设置为它和10的最大值,随后调用ensureExplicitCapacity
方法,如果minCapacity
>elementData的长度(即现有的长度已经不能容纳新元素的添加了),就进入grow(minCapacity)
方法进行扩容。 - 在
grow(minCapacity)
方法中:
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
先定义新容量为原容量的1.5倍,如果新容量不够,那么直接扩充到需要的容量(minCapacity),如果需要的容量比MAX_ARRAY_SIZE还要大,那么直接扩容到Integer.MAX_VALUE。
- 最后调用
Arrays.copyOf
方法,创建扩容后容量的数组,把旧数据拷贝进来。
注意事项
- 如果已知ArrayList需要的大致长度,可以在创建对象时指定容量。
- 如果需要大量add/addAll操作,那么可以先使用
ensureCapacity
方法进行手动扩容,避免多次扩容带来计算开销。 - 谨慎使用
removeAll
,retainAll
方法