ArrayList是我们java编码过程中最常用的集合类型。它在java collection framework中的位置如下:
ArrayList是List的可变大小实现,它实现了List的所有可选操作,允许所有元素,包括null。其内部使用一维数组存储数据,因此可以通过索引随机访问。size()
, isEmpty()
, get()
, set()
, iterator()
和listIterator()
操作的复杂度都是O(1)
。
ArrayList可以在插入数据前,通过ensureCapacity
一次性分配足够的空间,而不会因为随着输入逐个插入而不断进行增加空间的操作而浪费性能。
ArrayList是线程不安全的,如果需要线程安全的List,如果需要线程安全的实现,需要使用Collections.synchronizedList
方法包装,这个方法最好在ArrayList创建的时候就执行:
List list = Collections.synchronizedList(new ArrayList(...));
在并发修改ArrayList时,迭代器会快速而直接地抛出ConcurrentModificationException
异常,而不是在后续不确定的时间带来不确定的风险。
ArrayList内部属性
ArrayList继承自AbstractList,实现了RandomAccess接口、List接口、Serializable接口和Cloneable接口,这表明:
- ArrayList是一个可以“随机访问”的List
- ArrayList可以被序列化到流
- ArrayList可以调用clone()方法进行浅拷贝
它定义了以下几个常量:
//ArrayList的默认容量是10,相比较,HashMap初始容量为16
private static final int DEFAULT_CAPACITY = 10;
//用于空实例的共享数组,用在initialCapacity为0时的ArrayList的初始化
private static final Object[] EMPTY_ELEMENTDATA = {};
//用于默认大小的空实例的共享数据数组,和EMPTY_ELEMENTDATA区分开,以知道第一个元素加进来之后,ArrayList扩展了多少,用于无参ArrayList()初始化ArrayList
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//用来存储ArrayList的数据,默认容量初始化时,即为DEFAULTCAPACITY_EMPTY_ELEMENTDATA;非私有以方便内部类范围数据。
//ArrayList实际上是一个数组,所以通过数组下标(索引)直接访问数据。
transient Object[] elementData;
//ArrayList的尺寸
private int size;
//ArrayList的最大容量,由于有些JVM实现中,array有一些头部信息,所以最大值**不是**Integer.MAX_VALUE - 1
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
ArrayList的构造函数
无参构造函数,默认容量为10.
public ArrayList() {
this.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("IllegalCapacity: " + initialCapacity);
}
}
通过其他指定的集合作为构造函数参数,将提供的集合转成数组返回给elementData
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;
}
}
private方法总结
容量相关的方法:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
总结,在ensureCapacity
操作中,ArrayList实例原来的容量为oldCapacity,操作后容量为newCapacity,则newCapacity = oldCapacity + (oldCapacity >> 1);
,当然,newCapacity要在MAX_ARRAY_SIZE
以内。
ArrayList的实现中,所有修改操作前,都要进行ensureCapacityInternal
以保证容量。并对外提供void ensureCapacity(int minCapacity)
的public方法供开发者调用。
索引检查
所有的和具体的索引修改的操作,都需要坚持index在ArrayList的存储数组范围内。index范围检查的方法包括rangeCheck(int)
和rangeCheckForAdd(int)
,如果索引超出范围,会抛出IndexOutOfBoundsException
异常。
public方法总结
add方法
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++;
}
先检查index是否合法,然后给ArrayList扩容,调用System.arraycopy
将索引为index
之后的子数组后移一个单位,并将数组中索引为index
元素的值设置为element
。System.arraycopy
是一个本地方法,描述如下:
其作用就是放数组src
的第srcPos
个及之后length
个元素拷贝到dest
数组的第destPos
之后。
上面的代码显示,ArrayList增加元素的操作分为几个步骤:
- 增加缓存数组的大小:
ensureCapacityInternal
- 将
index
位置及以后的元素后移一个索引 - 将
index
位置设置为指定的element
size++
一个add操作分为四个步骤,所以不满足线程安全的条件:原子性。这也可以说是ArrayList线程不安全的理由。
基于索引的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;
}
ArrayList的实现直接将index
之后的元素前移一个单位,覆盖原来第index
个元素,将size
减一,原尾部元素设为null,返回被删除的元素应用。删除元素导致数组长度变化,因此不能通过索引在for循环中删除元素。
基于元素的remove方法
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;
}
如果ArrayList中有该元素,通过该元素第一次出现的位置,调用void fastRemove(int index)
删除第一次出现的这个元素,并返回true,如果没有找到,则返回fasle。本方法允许删除值为null的元素。
修改某个索引的数据
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
直接将对应索引的元素设置为指定的值,返回旧的元素。
removeAll方法
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
从此ArrayList中删除所有包含在指定集合中的所有元素。与之相法的方法是boolean retainAll(Collection<?> c)
,后者是保留所有包含在指定集合中的当前ArrayList中的元素,删除其它元素。
removeRange方法
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;
}
删除ArrayList中,索引从fromIndex
(包含)到toIndex
(不包含)中间的所有元素。
addAll方法
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;
}
将给定的集合c
按其循序插入到本ArrayList的第index
的位置,本ArrayList远index
之后的数据直接跟在c的尾部。
toArray,将ArrayList转换为数组
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
这个方法直接将ArrayList的存储数组前size
个元素到新数组并返回,实现中利用了Arrays.copyOf
方法。
toArray(T[] a)方法
public <T> T[] toArray(T[] a) {
if (a.length < size)
// 创建一个新的运行时类型数组,但内容内容是当前ArrayList的:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
如果传入数组a
的长度小于size
,返回一个新的数组,大小为size,类型与传入数组a
相同。所传入数组长度与size相等,则将elementData复制到传入数组中并返回传入的数组。若传入数组长度大于size,除了复制elementData外,还将把返回数组的第size之后的元素置为空。
如果数组a
的运行时类型不是此ArrayList中每个元素的运行时类型的超类,则抛出ArrayStoreException
异常。
浅拷贝方法
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);
}
}
返回当前ArrayList实例的浅拷贝,元素本身没有被复制。
其它
remvoeIf方法,删除所有满足指定判断的当前ArrayList中的元素。
sort方法,传入comparator
,将当前ArrayList按给定的比较器排序。
subList方法,回指定索引(fromIndex和toIndex, 前闭后开)之间的部分视图。如果fromIndex和toIndex相等,返回长度为0的list。返回的List是原来List中的一部分的视图的引用。任何对返回的subList的修改都将同步到原List中,反之亦然。
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
内部类
ArrayList的内部类包括两个迭代器:private class Itr implements Iterator<E>
和private class ListItr extends Itr implements ListIterator<E>
,以及一个子类类型private class SubList extends AbstractList<E> implements RandomAccess
,它们是AbstractList中对应内部类的优化版本,不在赘述。