顶部注释告诉我们的信息
-
ArrayList是实现了List接口的可变长数组(ArrayList的底层是基于数组实现的)
-
ArrayList允许内部存在null元素
-
ArrayList除了不是线程安全的之外,和Vector基本差不多。(如果想使用线程安全的ArrayList:List list = Collections.synchronizedList(new ArrayList(...));或者使用CopyOnWriteArrayList
-
size、isEmpty、get、set、iterator和listIterator方法都以固定时间运行,时间复杂度为O(1)。add和remove方法需要O(n)时间。与用于LinkedList实现的常数因子相比,此实现的常数因子较低。
-
ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。
-
每个ArrayList实例都有一个capacity属性,随着元素不断add到ArrayList,capacity会自动增加
-
在添加大量元素之前,可以使用ensureCapacity操作来增加ArrayList的容量,这可以减少递增的重新分配次数(因为grow的原理是使用Arrays.copyof(),一直grow的话肯定效率不高)
-
ArrayList的iterator和listIterator方法返回的迭代器是fail-fast的。
-
fail-fast不能保证一定会被执行的,所以依赖于fail-fast抛出的异常的程序都是错误的写法。fail-fast抛出的异常只能用来帮你detect bugs
源码部分
一、定义
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
-
继承于AbstractList类,AbstractList提供了List接口的骨干实现,以最大程度减少ArrayList实现随机访问所需的工作
-
实现了RandomAccess接口,这是个Marker Interface,用来表示List支持常数时间内的快速随机访问,用index去遍历实现了RandomAccess接口的类是要比用iterator遍历快的
-
实现了Cloneable接口,表明其可以调用clone()方法来返回实例的field-for-field拷贝
-
实现了java.io.Serializable接口,表明该类具有序列化功能(transient关键字表示:这个实例序列化时不要序列化transient修饰的属性)
二、属性
//1.序列化id
private static final long serialVersionUID = 8683452581122892189L;
//2.默认的初始容量
private static final int DEFAULT_CAPACITY = 10;
//3.存放空实例的空数组(当指定该ArrayList容量为0的时候,返回该空数组)
private static final Object[] EMPTY_ELEMENTDATA = {};
//4.存放capacity为默认大小的空实例的数组,比较4和3来确定:当给5中加入第一个元素的时候,5应该有多大
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//5.当前数据存放的位置,这个数组是不参与序列化的,ArrayList有自己专门的序列化方法
transient Object[] elementData;
//6.数组的长度,等于elementData.length
private int size;
//7.数组最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
关于属性要注意的几点:
-
除了上面的几个属性之外,还有一个从AbstractList继承过来的modCount属性,代表ArrayList修改的次数
-
指定ArrayList容量为0的时候,返回EMPTY_ELEMENTDATA数组
-
DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组是ArrayList的无参构造方法返回的数组。
-
DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA的区别是:第一个是无参构造方法返回的(即默认返回的),第二个是当用户指定容量为0的时候返回的
-
当elementData的值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,当第一次添加元素进入ArrayList中时,数组将扩容值DEFAULT_CAPACITY
-
elementData被标记为transient,ArrayList的序列化和反序列化不依赖于这个数组,详情请查看writeObject(java.io.ObjectOutputStream s)和readObject(java.io.ObjectOutputStream s)方法。
三、构造方法
无参构造方法:构造一个初始容量为10的空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
有参构造方法,参数为容量:构造一个容量为initialCapacity的空ArrayList
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);
}
}
有参构造方法,参数为Collection:构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
步骤:
(1)将collection对象转换成数组,然后将数组的地址赋值给elementData
(2)更新size的值,同时判断size的大小,如果是size等于0,直接将空对象EMPTY_ELEMENTDATA的地址赋给elementData
(3)如果size的值大于0,判断toArray方法是否成功了。如果没成功则执行Arrays.copyof方法,把collection对象的内容(可以理解为深拷贝)copy到elementData中。
注意:this.elementData = arg0.toArray(); 这里执行的简单赋值时浅拷贝,所以要执行Arrays,copyof做深拷贝
四、其他核心方法
get方法
ArrayList底层为数组,get方法很简单:先判断一下是否越界,之后直接通过数组下标来获取元素,时间复杂度为O(1),代码如下:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
set方法:和get类似,时间复杂度为O(1),代码如下:
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
contains方法
注意:这里的indexof方法是用数组索引做的,并不是像AbstractList中的indexof的实现方法(iterator遍历实现的)
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理,ArrayList中允许元素为null。
add(E e)方法:在ArrayList的末尾加入元素e,时间复杂度O(1)
注意:只加1,保证资源不被浪费
public boolean add(E e) {
//确认list容量,如果不够,容量加1。注意:只加1,保证资源不被浪费
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
add(int index, E element):在指定index下插入元素,时间复杂度O(n)
public void add(int index, E element) {
rangeCheckForAdd(index);
//确认list容量,如果不够,容量加1。注意:只加1,保证资源不被浪费
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
remove:时间复杂度O(n)
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;
}
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
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
}
注意:为了让GC起作用,必须显式的为最后一个位置赋null值。上面代码中如果不手动赋null值,除非对应的位置被其他元素覆盖,否则原来的对象就一直不会被回收。
clear
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
sort方法
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
五、扩容(很重要,核心部分)
ensureCapacity方法
这个方法是在外部使用的ensure容量方法(public)
情况1:在刚刚执行完ArrayList无参构造方法时,elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA。如果还没对这个list增加元素之前执行了ensureCapacity方法,将minExpand赋值为10,比较传进来的参数和10的大小,如果传进来的参数大于10,执行ensureExplicitCapacity,如果传进来的参数小于10,就啥也不做了
情况2:elementData不为空时(已经执行过add方法),minExpand=0,只要传进来的参数大于0,就一定会执行ensureExplicitCapacity
/**
* @param minCapacity the desired minimum capacity
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
ensureExplicitCapacity方法(private的)
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
grow方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
-
将容量变成原来的1.5倍,如果变成1.5还比想要的小,就将容量变成想要的;如果变成1.5倍后比MAX_ARRAY_SIZE大(这个是前面提到的ArrayList的属性),执行hugeCapacity方法,返回newCapacity
-
在确定好newCapacity之后,执行Arrays.copyof方法,将返回的数组赋值给elementData
-
hugeCapacity方法如下:
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
- 为什么MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;呢?因为某些VM会在数组中保留一些头字,尝试分配这个最大存储容量,可能会导致array容量大于VM的limit,最终导致OutOfMemoryError。
ensureCapacityInternal方法(顾名思义在内部使用的,add使用的就是这个ensure方法,private)
和ensureCapacity方法类似,也是分两种情况,如果elementData内已经包含元素了,就用minCapacity去ensureExplicitCapacity
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
扩容操作需要调用 Arrays.copyOf() 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。
六、序列化与反序列化
-
ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。
-
每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
七、迭代器
-
iterator()方法返回的是内部类Itr的实例,Itr是An optimized version of AbstractList.Itr
-
listIterator()方法返回的是内部类ListItr的实例,An optimized version of AbstractList.ListItr
主要讲一下fail-fast:
modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。代码参考上节序列化中的 writeObject() 方法。
八、System.arraycopy 和 Arrays.copyOf
-
System.arraycopy底层是c实现的
-
Arrays.copyOf底层也是调用System.arraycopy实现的
九、总结
-
ArrayList基于数组方式实现,无容量的限制(会扩容)
-
添加元素时可能要扩容(所以最好预判一下)
-
删除元素时不会减少容量(若希望减少容量,trimToSize())
-
删除元素时,将删除掉的位置元素置为null,下次gc就会回收这些元素所占的内存空间
-
remove(Object o)需要遍历数组
-
remove(int index)不需要遍历数组,只需判断index是否符合条件即可,效率比remove(Object o)高
-
线程不安全,使用iterator遍历可能会引发多线程异常