简单概括了ArrayList特点之后,正式开启源码解读之旅
(最前面有一大坨英文,有时间再慢慢翻译,如果觉得有必要的话~)
首先看ArrayList的继承关系和声明
她继承于AbstractList,实现了List, RandomAccess, Cloneable,java.io.Serializable这些接口。
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandomAccess接口,即提供了随机访问功能。RandomAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。
ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输,但其中一个属性是不允许被序列化的哦,接下来可以看到具体是哪个属性。
和Vector不同,ArrayList中的操作不是线程安全的。所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
ArrayList的属性 private static final long serialVersionUID = 8683452581122892189L;
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
private transient Object[] elementData;
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
第一个长整形serialVersionUID是为序列化使用的,所以其实只定义了三个属性,对象数组elementData(存储了ArrayList的元素,ArrayList的容量其实就是这个数组的长度),整型size(存储ArrayList中元素的实际容量), 私有静态final类型MAX_ARRAY_SIZE,是为ArrayList定义动态扩充容量的上限。这里也许有人会有疑问,为什么不直接定义成整型的最大值,而是要定义成整型最大值-8呢?原因是某些特定环境下数组里会默认保留一些头信息,ArrayList在设定最大容量时需要将这些信息的长度排除掉,否则可能会出现明明没到整型最大值,但仍抛出OutOfMemoryError异常。
需要注意的是,这里的elementData是用关键字transient修饰的,使用transient修饰是用来表示一个域不是该对象串行化的一部分,即当ArrayList被串行化的时候,elementData的值不包括在串行化表示中,而非transient的变量size则是被包括进去的。那么问题来了,为什么ArrayList中的元素不能被串行化呢?Mark一下留待以后解决~
ArrayList的构造函数
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this(10);
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
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);
}
有三个:
ArrayList(int initalCapacity) :带参数的构造函数
使用initalCapacity来初始化elementData数组的大小,若小于零,抛出IllegalArgumentException异常;
ArrayList():默认的构造函数
使用带参数的构造函数初始化一个大小为10的ArrayList;
ArrayList(Collection<E> c)
先将提供的几何转变为数组,c.toArray()方法有可能返回的不是对象数组,这种情况下使用Arrays.copyOf转为对象数组。
ArrayList的其他方法
1. trimToSize()
/**
* Trims the capacity of this <tt>ArrayList</tt> instance to be the
* list's current size. An application can use this operation to minimize
* the storage of an <tt>ArrayList</tt> instance.
*/
public void trimToSize() {
modCount++;
int oldCapacity = elementData.length;
if (size < oldCapacity) {
elementData = Arrays.copyOf(elementData, size);
}
}
有些时候对象数组的长度跟ArrayList定义的size属性不一定是相等的,那么可以显式地调用此方法确保二者的统一,从而避免不必要的空间浪费。
2.add(E): 在数组的末尾插入元素
add(int, E):在指定位置插入元素
这些大家应该都知道,因为这两个方法比较常用。但是他们是如何实现的呢?
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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(E), 将新的元素e插入到size的位置,然后将size+1备用,返回添加成功。
add(int,E) 先检查你要插入的位置index,如果小于0或是比ArrayList的size还大,则抛出IndexOutOfBoundsException异常,这个异常我相信大家都有见过。然后调用System.arraycopy将原数组elementData的index位置到size-index位置之间的元素移动到index+1开始以后的位置去,这么说有点绕口,说白了就是将elementData数组里index以后的元素向后平移一个位置,空出index这个位置给要增加的元素element,然后将size属性加1。
比较两个方法我们可以发现二者都使用到了ensureCapacityInternal(size + 1)方法,那么这个方法不说大家也可以猜出来,是扩充容量用的。怎么实现的,看源码:
/**
* Increases the capacity of this <tt>ArrayList</tt> instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
public void ensureCapacity(int minCapacity) {
if (minCapacity > 0)
ensureCapacityInternal(minCapacity);
}
private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
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;
}
第一个方法是ArrayList暴露出来让你自己扩充容量用的,目前我没使用过,都是让ArrayList内部帮我扩充容量。
真正起作用的是剩下的三个私有方法,综合起来看:我在size的基础上增加一位(因为要add一个元素么),但是发现这时候size+1的值比当前对象数组的长度还要大,此时就要去扩充数组容量了,进入了grow方法,她会根据规则对数组对象进行扩充。先说明一下两个值:
A:minCapacity,就是当前size+1后的值,要求扩充到多大;B:newCapacity,当前数组对象长度*3/2(JKD1.7以前的版本是用算数表达式实现的,1.7的版本变成了位右移表达式,结果是一样的,熟悉位移的童鞋应该知道,这么改是有助于提高执行效率和内存使用的)
扩充的规则是:
如果B<A:不够我要求扩充的容量,那么就按照A来扩充。一般不会有,但特殊情况比如当前size的值比实际数组长度大(没有用trimToSize()释放)。
如果A<B<MAX_ARRAY_SIZE: 这个是正常情况下默认扩充的容量大小,即原数组对象的3/2,即扩充了原长度的一半。
如果B>MAX_ARRAY_SIZE:扩充不了原长度的一半了,则使用上限值去扩充。
为什么说ArrayList是一个动态数组,答案就在这几个方法里面。
那么同理add(Collection)和add(int,Collection)实现原理是一样的,无非多了一个将Collection转换成数组的操作,这里不再赘述。
3 size()
/**
* Returns the number of elements in this list.
*
* @return the number of elements in this list
*/
public int size() {
return size;
}
这里使用这个public方法封装size私有属性,返回的是当前ArrayList的大小,这点也跟数组的length属性区别开了。
4 isEmpty()
/**
* Returns <tt>true</tt> if this list contains no elements.
*
* @return <tt>true</tt> if this list contains no elements
*/
public boolean isEmpty() {
return size == 0;
}
返回ArrayList是否为空。
5 contains(Object) /**
* Returns <tt>true</tt> if this list contains the specified element.
* More formally, returns <tt>true</tt> if and only if this list contains
* at least one element <tt>e</tt> such that
* <tt>(o==null ? e==null : o.equals(e))</tt>.
*
* @param o element whose presence in this list is to be tested
* @return <tt>true</tt> if this list contains the specified element
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
使用indexOf方法来判断当前数组是否包含元素o,接着来看indexOf方法。
6 indexOf(Object)
lastIndexOf(Object)
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*/
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;
}
/**
* Returns the index of the last occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the highest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*/
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;
}
使用了for循环对数组进行遍历,indexOf和lastIndexOf无非是前者从前往后遍历,后者从后往前遍历,如果当前元素与o相等,则返回当前元素索引。需要注意的是,ArrayList是支持对null的查找的。
这里多说一下ArrayList的遍历方式,有三种
第一种:利用迭代器遍历。即通过Iterator去遍历
Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
value = (Integer)iter.next();
}
第二种,随机访问,通过索引值遍历(由于实现了RandomAccess接口,支持通过索引访问元素)
Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
value = (Integer)list.get(i);
}
第三种,for循环遍历。
Integer value = null;
for (Integer integ:list) {
value = integ;
}
这三种遍历方式,使用随机访问(即通过索引序号访问)效率最高,而使用迭代器效率最低。至于为什么,有兴趣的童鞋可以下去研究一下。
7 clone()
/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone() {
try {
@SuppressWarnings("unchecked")
ArrayList<E> v = (ArrayList<E>) 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();
}
}
调用父类的clone方法返回一个对象的副本,将返回对象的elementData数组的内容赋值为原对象elementData数组的内容
8 toArray()
toArray(T[])
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
@SuppressWarnings("unchecked")
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;
}
toArray() 调用Arrays.copyOf方法返回一个数组,内容是size个elementData元素,即把elementData中从0到size-1的元素拷贝到新的数组并返回。
toArray(T[]) 如果传入数组的长度小于size,则返回一个新的数组,大小为size,类型与传入数组相同;如果传入数组的长度大于或等于size,则将elementData全部拷贝到a中并返回,其中如果传入数组长度大于size,除了复制外,还将返回数组a中的size位置置为null。
9 elementData(int)
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
返回索引为index的数组元素
10 get(int)
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
先校验index是否越界,不越界就返回当前索引为index的数组元素。
11 set(int, E)
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
先校验index是否越界,不越界就将索引为index的数组元素另存为一个副本oldValue,然后将入参element插入到该索引位置,返回副本值oldValue。原本以为调用set方法无返回值的童鞋是不是很诧异?
12 clear()
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
modCount++;
// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
用一个for循环遍历,将所有的元素设置为null,然后将size清零。这里需要注意的是数组elementData的实际长度并没有改变,此时虽然arraylist的大小为0,但仍然占用elementData.length的空间,这就是前面提到的trimToSize方法来释放多余占用的空间。
13 remove(int)
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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; // Let gc do its work
return oldValue;
}
先index索引越界校验,保存原来index位置数组元素的副本oldValue,然后如果index跟elementData最后一位索引值相等,即移除最后一个元素,更新size,否则在移除元素前将数组index之后的所有元素向前移动一位。移除成功后返回移除的元素值
14 remove(Object)
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
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 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; // Let gc do its work
}
这个方法的作用是remove一个元素对象。那么就需要找到这个元素在数组中位置index,然后remove(index)就行了,查找index的方法大家是不是看着眼熟,其实就是前面讲到的indexOf(Object)方法,只不过找到以后多了一个移除的动作。那么问题来了,移除用了一个新的方法fastRemove,而没用之前写好的remove方法,这是因为此时已经确定找到了index,就不需要像remove方法里先校验一次是否越界了。
15 removeRange(int,int)
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// Let gc do its work
int newSize = size - (toIndex-fromIndex);
while (size != newSize)
elementData[--size] = null;
}
方法的执行过程是
将elementData从toIndex位置开始的元素向前移动到fromIndex,然后将toIndex位置之后的元素全部置空顺便修改size。
但是为什么设置为protected受保护的方法呢?下面这个链接做了解释,但看不怎么明白,因为JDK1.7已经在ArrayList方法里实现了SubList,那么绕一大圈貌似没什么卵用。大家可以交流下自己的理解。
http://www.cnblogs.com/hzmark/archive/2012/12/19/ArrayList_removeRange.html
16 removeAll(Collection)
retainAll(Collection)
public boolean removeAll(Collection<?> c) {
return batchRemove(c, false);
}
public boolean retainAll(Collection<?> 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) {
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
删除或保留ArrayList中包含Collection c中的的元素,这两个方法都依赖batchRemove(Collection<?> c, boolean complement)实现。
看到这里是该说明一下modCount了,这个是个什么鬼?
其实在add()、remove()、addall()、removerange()及clear()方法都会让modCount增长,在add()及addall()方法的对modCount的操作在ensureCapacityInternal中。 modCount用于记录ArrayList的结构性变化的次数,也用于序列化。
/**
* Save the state of the <tt>ArrayList</tt> instance to a stream (that
* is, serialize it).
*
* @serialData The length of the array backing the <tt>ArrayList</tt>
* instance is emitted (int), followed by all of its elements
* (each an <tt>Object</tt>) in the proper order.
*/
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 array length
s.writeInt(elementData.length);
// 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();
}
}
该方法是将ArrayList保存为流,即对modCount,数组长度及元素进行序列化操作。还是个private方法,估计是ArrayList内部使用的。
/**
* Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in array length and allocate array
int arrayLength = s.readInt();
Object[] a = elementData = new Object[arrayLength];
// Read in all elements in the proper order.
for (int i=0; i<size; i++)
a[i] = s.readObject();
}
有了writeObject,那么对应就有readObject,将ArrayList反序列化。
虽然洋洋洒洒写了这么多,几乎将源码粘出来了,但还是有个别细节问题模棱两可,比如ArrayList三种遍历方式效率的比较,removeRange为什么是一个保护方法而不能直接使用,内部私有类Itr,ListItr,SubList及其使用等,文章只是浅显地罗列了我们工作中常用的ArrayList的方法和特性,更细节深入的东西留待以后补充。