ArrayList简介
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList 是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。ArrayList允许包括null在内的所有元素。
ArrayList 的 size、isEmpty、get、set、iterator 和 listIterator 操作都以固定时间运行。add操作以分摊的固定时间运行,为什么说分摊?因为add操作有可能会导致数组扩容,总体来说,插入n个元素需要O(n)时间。
ArrayList 的 iterator 和 listIterator 方法返回的迭代器是fail-fast的:modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化(如 set 方法)。在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。
ArrayList构造函数
// 自定义初始容量
public ArrayList(int initialCapacity)
// 默认构造函数
public ArrayList()
// 创建一个包含Collection元素的ArrayList
public ArrayList(Collection<? extends E> c)
ArrayList的继承关系图
![](https://i-blog.csdnimg.cn/blog_migrate/8d7773ab6146a30930c85d577d96825f.png)
ArrayList部分源码分析
默认容量大小是10
private static final int DEFAULT_CAPACITY = 10;
所有空的 ArrayList 共享一个空数组(给非默认初始容量的空数组使用)
private static final Object[] EMPTY_ELEMENTDATA = {};
所有空的 ArrayList 共享一个空数组(给默认初始容量的空数组使用)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
真正存储数据的数组,使用 transient 修饰,该关键字声明数组默认不会被序列化。ArrayList 具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData;
集合中元素的个数
private int size;
tri
mToSize 方法将此 ArrayList 实例的容量调整为列表的当前大小(即有数据的部分)
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
ensureCapacity 方法确保ArrayList有足够的容量,如果没有足够容量会进行扩容,调用ensureExplicitCapacity方法。在这里只要elementData 不是给默认容量(10)使用的空数组,都会调用到ensureExplicitCapacity方法。如果是默认容量的空数组则会比较一下传入的minCapacity是否大于10。ensureCapacityInternal 为私有方法,当添加数据时会被调用,如果初始为默认初始容量的空数组,则会直接扩大到长度为10,所以在调用默认构造器初始化数组时并没有new数组,而是将 elementData 指向了一个空数组
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
ensureExplicitCapacity 方法简单判断了一下传入的容量是否大于当前存储数据的elementData数组的长度,如果大于这个长度才调用grow方法真正扩大数组的长度,newCapacity 实际有可能为3个值:原数组长度的1.5倍;传入的长度;MAX_ARRAY_SIZE 的长度。扩容操作需要把原数组整个复制到新数组中,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int 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);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
数组的最大长度比Integer的最大值小8,这是因为有些vm会在数组中保存一些头信息
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
contains: 通过遍历数组来判断集合中是否包含某个元素,耗时和数组的长度有关,返回布尔类型的值。indexOf: 通过遍历数组来判断集合中是否包含某个元素,耗时和数组的长度有关,返回元素所在的下标。lastIndexOf: 和indexof类似,这是从后向前遍历数组
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;
}
这里的 clone 是浅拷贝
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
如果 a 的长度小于当前 ArrayList 中元素的个数,则直接返回一个用当前 ArrayList 拷贝的数组。反之,则将当前数组拷贝到传入数组 a 中,并且将拷贝结束后的位置的值置为 null
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
get 方法在检查完 index 是否合法后,调用 elementData 方法,通过数组寻址获取数据,效率很高E elementData(int index) {
return (E) elementData[index];
}
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
set 方法与 get 方法类似,同样高效,同时会返回 oldValue
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
add(E e) 方法直接将值加到数组末尾,注意在 ensureCapacityInternal 方法中会将 modCount+1。add(int index, E element) 方法在指定位置添加元素,首先判断 index 的范围,确保数组容量足够,然后将数组中从 index 开始的数据都向后移动一位,然后将 index 位置设置成插入的 element
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
remove(int index) 方法,先取得 index 位置的 value,用于删除后返回。然后把 index+1 开始的所有数据左移一位,然后size-1,并且将数组中 size-1 的位置设置为 null方便虚拟机GC。remove(Object o) 是删除一个对象,像查找一样,需要遍历数组查找第一个和它相同的对象的index,然后调用 fastRemove 直接将它删除,fastRemove 和 remove 非常类似,只是不做过多的判断,效率更高,但只能内部使用
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
}
addAll(Collection<? extends E> c) 方法调用的底层的 System.arraycopy 方法进行拷贝。addAll(int index, Collection<? extends E> c) 方法内部需要调用两次 System.arraycopy,第一次将 elementData 的index位置开始的数据向后移动,为待插入的集合腾出位置,然后再调用一次,将待插入集合所有数据复制到从index开始的区域中
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
区域删除也是类似,先将 toIndex 后的数据都移动到 fromIndex 开始的区域上,然后将
newSize
位置以后的数据都置为null,并且重置 size 的值
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
removeAll 和 retainAll 都是调用的 batchRemove 方法,只是传入的complement参数不同。在 batchRemove 方法中会按照size遍历数组,判断集合 c 是否包含这个元素,如果这个 boolean 值和传入的 complement 参数相等(即 removeAll 是求差集,retainAll 是求交集),则从数组的头位置依次保存这些元素。在遍历完后 finally 代码块中再进行操作,注意在这里首先判断了如果 try 中出错的情况,如果出错了,则 r 一定不等于 size,则将数组出错位置 r 以后的所有元素复制到数组的 w 位置开始的区域,并将 w 置为复制完成后最后一个元素的位置。然后再将数组中 w 后的位置全部置位 null,并重新调整 size 的值。
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
iterator 方法和 listIterator 方法分别返回了 Itr 类和 ListItr 类。Itr使用了游标 cursor 来维护当前元素,以实现 next 等方法。ListItr继承自Itr,但是提供了向前遍历的方法 previous
public ListIterator<E> listIterator() {
return new ListItr(0);
}
public Iterator<E> iterator() {
return new Itr();
}
subList 方法返回指定范围的一个子集合(这是一个内部类,SubList中维护了父 List 的引用,并且记录了一个偏移量 offset,以便确认索引在父 List 中的真实索引,例如在 SubList 的 set 方法中,所以subList方法并没有生成一个新的数组),前闭后开
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
sort方法直接调用了Arrays.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++;
}