一,ArrayList简述
ArrayList是实现了List接口的动态数组,动态数组是指它的大小是可变的。ArrayList实现了所有可选列表操作,并允许保存包括null在内的所有元素。ArrayList除了实现List接口,还提供了操作是内部用来存储列表的数组的大小的方法。
每个ArrayList实例都有一个容量(capacity),该容量是用来表示存储元素的数组的大小。随着元素的增加,ArrayList的容量也会随之扩容,但这并不是说每次向ArrayList中增加一个元素,ArrayList的容量就会扩容,而是先会判断元素数量是否达到了最小容量,如果达到了才会去扩大容量(capacity)。扩容会带来数组拷贝的操作,所以当你知道具体的业务数据量的时候,指定一个初始的容量大小是很有必要的。对了,ArrayList默认的容量大小是10,所以在构造ArrayList对象时,可以指定一个初始化容量,这样可以减少扩容时数据的拷贝问题。当在添加大量元素前,可以使用ensureCapacity()方法来增加 ArrayList 实例的容量,这可以减少递增式再分配的数量。
注意,ArrayList 实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表(从结构上改变列表是指增加元素、删除元素、改变了ArrayList容量。如果只是改变 了某个元素的值不属于该操作的范畴。),那么它必须保持外部同步。所以为了保证同步,最好的办法是在创建时完成,以防止意外对列表进行不同步的访问:
List list = Collections.synchronizedList(new ArrayList(…));
二,ArrayList源码解析
想必,大家多ArrayList的使用都很熟悉了,它的一些基本操作我们就不讲了,我们主要看下它底层的实现。ArrayList是List接口的实现,它底层是采用数组来实现的,所以对它的操作也是基于对数组的操作。下面我们就通过源码来说明ArrayList的底层实现,PS:本文源码是基于OpenJDK 1.7。
2.1、ArrayList属性:
在ArrayList中定义了四个属性,分别如下
:
private
static
final
int
DEFAULT_CAPACITY
= 10;//默认容量
private static final Object[] EMPTY_ELEMENTDATA = {};//空ArrayList实例对应的数组
private
transient
Object
[]
elementData
;//这就ArrayList真正存储元素的数组,ArrayList的容量就是该数组的长度。
private
int
size
;//ArrayList的大小,也是ArrayList中存储元素的数量
这里有个需要注意的地方:elementData使用了transient关键字。通过源码你会发现ArrayList实现了Serializable接口,我们知道实现了Serializable接口的对象可以通过序列化操作实现持久化,但是使用了transient关键字的属性在序列化操作时是不会被持久化,同时在反序列化时,此属性也不会被恢复。也就是说在对ArrayList实例在反序列化操作后得到是实例中ArrayList中的elementData是为null。
2.2、构造方法以及主要的方法
2.2.1、构造方法
- ArrayList():此构造方法是没有指定初始容量的,这个时候ArrayList的容量就是默认大小——10
public ArrayList() {
super ();
this .elementData = EMPTY_ELEMENTDATA; }
- ArrayList(int initialCapacity):此构造方法指定了初始容量大小。
public ArrayList( int initialCapacity) {
super ();
if (initialCapacity < 0)
throw new IllegalArgumentException( “Illegal Capacity: “ + initialCapacity);
this .elementData = new Object[initialCapacity]; }
- ArrayList(Collection<? extends E> c) :此构造方法是构造一个包含指定集合元素的ArrayList实例,这些元素是按照该集合的迭代器返回的顺序排列的。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[]. class )
elementData = Arrays.copyOf(elementData, size, Object[]. class ); }
2.2.2、增加
ArrayList提供了四个向其中增加元素的方法,分别是:add(E e)、add(int index,E element)、add(Collection<? extends E> c)、add(int index,Collection<? extends E> c)。我们一个个分析:
- add(E e),此方法用于在列表的尾部添加一个元素,源码如下:
public boolean add(E e) {
ensureCapacityInternal (size + 1); // Increments modCount!!
elementData [size++] = e;
return true ; }
此方法会先判断要不要扩容,然后再将元素添加到数组中。我们看下判断是否要扩容的方法:
private void ensureCapacityInternal( int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity); } //此方法会先列表是不是空的,如果是空,则比较当前minCapacity和默认容量(DEFAULT_CAPACITY)的大小,并将两者的中大的赋值给minCapacity。然后会调用ensureExplicitCapacity()来确定列表确切的容量。以下是ensureExplicitCapacity()的源码:
private void ensureExplicitCapacity( int minCapacity) {
modCount ++;
// overflow-conscious code
if (minCapacity – elementData.length > 0)
grow(minCapacity); } //此方法首先会改变modCount变量的值,该变量定义在ArrayList的父类——AbstractList中,用于标记列表被修改的次数。
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); }
//此方法就是扩容数组的方法,但是在扩容数组前肯定是有个逻辑判读的。首先会定义个变量newCapacity表示新的容量大小,它的初始值为数组elementData长度的1.5倍。 //然后将newCapacity和最小容量、最大数组长度的大小进行比较,得到newCapacity的最终的值。得到最终的容量大小后就会去进行数组拷贝动作,Array.copyOf()方法最终 //是会调用System.arraycopy(),System.arraycopy()是个本地方法,执行效率会比较高。Max_ARRAY_SIZE和hugeCapacity()的定义如下: private static final int MAX_ARRAY_SIZE = Integer . MAX_VALUE – 8; private static int hugeCapacity( int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE; }
- add(int index,E element) 此方法其实和add(E e)方法类似,只是多了index是否正确的判断和对原数组的移位处理,移位处理就是为了空出指定的index,然后添加元素,具体代码如下:
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++; }
- add(Collection<? extends E> c)、add(int index,Collection<? extends E> c)。第一个表示在列表尾部开始添加集合c中的元素,第二个方法表示在指定的index添加集合c中的元素到列表。这两个方法原理其实和前面的两个add方法都是一样的,他们实际上都是对底层的数组进行操作,这里就不过多的去讲解了,感兴趣的同学可以去看下他们的源码,在这我也不贴出来了。对了,一般我看源码的地方是grepcode,这个网站很多Java源码,可以多看看。
2.2.3、删除
ArrayList提供了remove(Object obj)、remove(int index)、removeAll(Collection c)、removeRange(int fromIndex,int toIndex)、retainAll(Collection c )、clear()。
remove(Object obj)方法是移除列表中首次出现的指定的元素,如果元素存在于列表中的话。remove(int index),移除指定位置的元素。removeAll(Collection c),将集合c中包含的元素从列表中移除。removeRange(int fromIndex, int toIndex),将列表中索引在fromIndex(包括该索引)和toIndex(不包括该索引)之间的元素从列表中移除。retainAll(Collection c),只保留集合c中含有的元素,也就是说将列表中除集合c中包含的元素以外的元素全从移除。clear(),将列表中的元素全部清除。我们分析其中的几个:
- remove(int index)。直接看该方法的源码:
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; }
从代码中可以看出,remove(int index)方法中,首先会判断是否越界,然后改变modCount的值,这个值的含义我们在上面也提过;然后计算需要移动多少位(numMoved的值),再执行数组拷贝的操作;最后改变列表元素数量size值,并置空数组elementData的最后一个元素。
- remove(Object obj),用于删除列表中首次出现的指定元素obj,如果列表中存在该元素的话。
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 ; } //此方法首先会判断要删除的元素是不是为null。如果为null的话,找到数组中第一个为null的元素,然后快速删除,并返回true。如果不为null,也是找到数组中第一个和指定对象o相等的元素,然后快速删除,并返回true。如果两种情况下都没有找到指定的的元素o ,那么最终返回的就为false,表示没有找到要删除的元素。下面看下快速删除——fastRemove(int index)方法,其实这个方法的思想和remove(int index)的实现是一样的,最终都是通过数组的移位来实现的。
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
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 }
- removeAll(Collection c)、removeRange(int fromIndex,int toIndex)、retainAll(Collection c )、clear()这几个方法的具体实现就不多说了,原理都差不多,感兴趣的同学可以去看下源码。
2.2.4、查找
ArrayList中查找是通过get(int index)来获得指定位置的元素的,我们知道ArrayList的底层实现是数组,所以get(int index)的方法的实现就很简单了。先会判断给定的索引是否越界,然后返回数组elementData指定索引下的元素。
public E get( int index) {
rangeCheck (index);
return elementData(index); }
三,总结
ArrayList的底层实现是数组,对ArrayList的添加、删除、修改、查找实际上都是对ArrayList中的数组进行相应的操作。ArrayList的容量(大小)可以实现动态改变的原因是,底层会去动态改变实现ArrayList 的那个数组的大小。改变ArrayList容量的操作也叫扩容,扩容的规则是不一定的,OpenJdk中默认是原有元素的1.5倍,但是这个不是标准,如有具体的需要完全是可以自己重新定义这个扩容的倍数的。最后建议看ArrayList的源码的时候可以在本子上画一画常用方法的实现图,主要是数组拷贝那一块,这样理解起来比较直观方便。