无论大家是做Java后端还是Android,相信ArrayList在我们的日常工作中再熟悉不过了。提起ArrayList,我们首先会想到动态数组、线程不安全等特点。今天我们从源码的角度再来学习一下。
首先我们来看下ArrayList中几个重要的属性:
// 序列化ID
private static final long serialVersionUID = 8683452581122892189L;
// 默认初始化容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储数据元素的数组
transient Object[] elementData; // non-private to simplify nested class access
// 当前arraylist集合的大小,也就是elementData数组中数据元素的个数
private int size;
接着我们看下ArrayList的构造方法,ArrayList的构造方法支持三种形式:
/**
* 形式一:无参构造方法
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
我们可以看到,在构造方法中直接将 elementData 指向 DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组,这个时候该ArrayList的size为初始值0。
/**
* 形式二:携带一个int类型的参数,指定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);
}
}
参数initialCapacity为我们所指定的arraylist初始容量,可以看出,方法中对initialCapacity的值进行了一系列判断,当我们所指定的初始容量小于0时无意义,直接抛出非法参数异常。当initialCapacity大于0时,会直接创建一个Object类型的数组,数组的初始大小就是initialCapacity的值。当initialCapacity等于0时,会直接将elementData 指向EMPTY_ELEMENTDATA空数组。
/**
* 形式三:携带一个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;
}
}
代码中首先将Collection参数通过toArray方法转换成数组,并赋值给elementData,然后对arraylist中的size进行赋值并判断size是否等于0。当size为0时,直接将elementData 指向EMPTY_ELEMENTDATA空数组。当size不为0时执行copyOf方法。
ArrayList的构造方法到这里就结束了,接着我们分析下add方法。add方法根据参数个数的不同有两种,如下所示:
/**
* 方式一:直接添加数据元素到arraylist的尾部
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* 方式二:插入数据元素到特定的角标位置
*/
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
我们首先对方式一进行分析下。方法中首先调用到ensureCapacityInternal方法,将size+1作为参数传入。我们知道,size用来表示当前arraylist的大小,也就是elementData数组中元素的个数,size+1就是确保数据元素添加成功的最小容量。ensureCapacityInternal方法是做什么的?让我们跟进去看下:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
可以看到方法中首先将elementData 和DEFAULTCAPACITY_EMPTY_ELEMENTDATA进行对比,如果elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则执行 if 语句体的操作,否则直接调用ensureExplicitCapacity方法,将最小容量minCapacity作为参数传入。什么情况下elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA呢?不知道大家还有没有印象,当我们通过ArrayList的无参构造方法创建ArrayList对象时,在构造方法中会直接将elementData 指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA。也就是说当我们通过ArrayList的无参构造方法创建ArrayList对象后,再调用add方法的时候会执行if 语句体操作,将minCapacity 重新赋值为DEFAULT_CAPACITY和minCapacity中的最大值。DEFAULT_CAPACITY我们之前也有讲过,为默认初始化容量。当我们通过ArrayList的无参构造函数创建ArrayList对象后,首次调用add方法时,这个时候ensureCapacityInternal方法中传入的minCapacity为1,if 语句体中minCapacity重新赋值为10。我们接着看下ensureExplicitCapacity方法:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
可以看到,方法中首先将变量modCount自增1,modCount是做什么用的呢?其实modCount是用来标记当前arraylist集合操作变化的次数,在fail-fast机制中会有用到这个变量,关于fail-fast机制我们稍后会讲下。接着判断minCapacity - elementData.length 是否大于0,当minCapacity - elementData.length大于0的时候说明当前elementData数组大小不够用,需要扩容,grow方法就是具体的扩容操作,我们跟进去看下:
/**
* 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) {
// 1.首先获取到elementData数组的长度,作为原容量
int oldCapacity = elementData.length;
// 2.新容量 = 原容量 + 原容量/2; 1.5倍扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
// 3.若1.5倍扩容后还不够,则将最小容量作为新容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 4.限制最大容量
newCapacity = hugeCapacity(minCapacity);
// 5.进行原有数据元素copy处理
elementData = Arrays.copyOf(elementData, newCapacity);
}
关于grow方法扩容操作代码中已经注释很清楚了。接着我们回到add方法接着往下看。下面的代码就比较简单了,就是一个简单的数据元素赋值操作,注意size自增1,最后return true,表示添加元素成功。
上面我们分析了add方法的第一种方式,直接添加数据元素到arraylist的尾部。简单讲下,首先会处理elementData数组是否需要扩容操作,接着对新添加的数据元素进行赋值操作,size自增1,最后return true表示数据添加成功。add方法的第二种方式和第一种方式类似,插入数据元素到特定的角标位置,方法中首先会对下角标index 进行越界判断,然后会对elementData数组是否需要扩容操作进行处理,接着调用 System.arraycopy方法将指定角标后的元素后移一位,最后对指定角标位置进行赋值操作并将size自增1。
add方法到这里就结束了,下面我们接着看remove方法,remove方法根据参数的不同也同样分为两种,方法如下:
/*
* 方式一:根据角标进行remove操作
*/
public E remove(int index) {
// 1. 对角标越界进行判断
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
// 2.modCount自增1
modCount++;
// 3.获取到指定下角标位置的数据
E oldValue = (E) elementData[index];
// 4.计算需要移动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
// 5. 指定角标位置后的元素前移一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 6.将size自减1,并将数组末尾置为null,便于垃圾回收
elementData[--size] = null; // clear to let GC do its work
// 7.最后将所要删除的数据元素return掉
return oldValue;
}
/*
* 方式二:根据数据元素进行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;
}
对于方式一,根据角标进行remove操作,代码中的注释已经很清楚了,下面我们看下方式二,根据数据元素进行remove操作。方法中首先对当前remove的数据元素进行null判断。无论当前remove的数据元素是否为null,都需要一个for循环进行遍历操作,注意if 条件块的代码在数据元素为null和不为null两种情况下是不同的。如果elementData数组中某一角标处的元素等于当前remove的数据元素,会调用fastRemove方法进行具体remove操作,最后return true表示remove操作成功。否则return false表示remove操作失败。fastRemove方法的代码如下:
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
// 1.modCount的值自增1
modCount++;
// 2.计算需要移动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
// 3. 指定角标位置后的元素前移一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 4.将size自减1,并将数组末尾置为null,便于垃圾回收
elementData[--size] = null; // clear to let GC do its work
}
接着我们看下set方法:
public E set(int index, E element) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
相信set方法大家一看就明白了。我再说下,set方法需要接受两个参数,第一个参数index为下角标,第二个参数element为数据元素,表示将下角标index处的数据元素赋值为element。在方法中,首先对index下角标进行越界判断,然后获取到角标index处的原数据元素oldValue,接着将角标index处的数据元素赋值为element,最后将原有数据元素oldValue return掉。
接着就是get方法了:
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}
get方法中的操作更是简单,首先同样是进行越界判断,接着直接将下角标index处的数据元素return掉。
接着看下clear方法:
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
同样clear方法中的操作也很简单,首先将modCount的值自增1,然后进行一个遍历操作,将elementData数组角标元素置为null,最后将size的值置为0。
come on,我们接着看下contains方法:
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
what? ? ?,在方法内部直接将indexOf(o) >= 0作为结果return出去了,我们大家都知道,indexOf方法是将一个数据元素传入,返回该数据元素对应的下角标,如果数据元素在当前arraylist中不存在,则返回-1。也就是说当数据元素在当前arraylist中存在时,indexOf(o) >= 0成立,结果为true,否则为false。我们接着跟进去indexOf方法看下:
/**
* 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;
}
可以看到indexOf方法中对数据元素进行了null判断,两种情况下都进行了遍历操作,只不过两种情况下的if 条件语句不同。当满足条件时,直接将对应的下角标return掉,否则return -1,表示当前arraylist中不存在该元素。
接下来我们一起来看下fail-fast机制。什么是fail-fast机制呢?其实fail-fast机制是集合中的一种错误检测机制,我们在操作集合中经常会遇到 java.util.ConcurrentModificationException异常,产生该异常的原因就是fail-fast机制。我们先来看个例子先:
List mDatas = new ArrayList<String>();
mDatas.add("hello");
mDatas.add("android");
mDatas.add("java");
Iterator<String> mIterator = mDatas.iterator();
while (mIterator.hasNext()){
if ("android".equals(mIterator.next())){
mDatas.add("php");
}
}
上述代码运行后会出现什么结果呢?是不是在mDatas集合中存在四个元素,分别为:“hello” “android” “java” “php” 了呢?我们运行看下:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:903)
at java.util.ArrayList$Itr.next(ArrayList.java:853)
at com.example.halobear.eventbustest.ArrayListTest.main(ArrayListTest.java:21)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMainV2.main(AppMainV2.java:131)
可以看到程序直接crash掉了,抛出的异常正是我们刚才提到的ConcurrentModificationException 并发修改异常,正是由于fail-fast机制导致的。在什么情况下会出现该异常呢?我先简单说下,一般情况下有两种情况,一种情况发生在多线程操作,当a线程正在通过迭代器操作集合mDatas时,同时b线程对mDatas进行添加或者删除元素,会触发fail-fast机制,抛出该异常。另一种情况是在迭代集合mDatas的过程中对mDatas集合进行元素添加或者删除操作时,会触发fail-fast机制,抛出该异常。归根结底就是当我们进行集合数据迭代时,有其他操作修改了当前arraylist的modCount的值,导致modCount != expectedModCount,会抛出该异常。下面我们从源码角度看下:
我们通过arraylist的iterator()方法获取到迭代器,点进去:
/**
* Returns an iterator over the elements in this list in proper sequence.
*
* <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
return new Itr();
}
可以看到方法中直接创建了一个Itr实例对象,并return掉。我们跟进去:
private class Itr implements Iterator<E> {
protected int limit = ArrayList.this.size;
int cursor; // next 元素角标
int lastRet = -1; // last 元素角标
int expectedModCount = modCount; //重点:将modCount的值赋值给expectedModCount(期待修改数量)
// 判断是否有下一个元素
public boolean hasNext() {
return cursor < limit;
}
@SuppressWarnings("unchecked")
public E next() { // 获取到next元素
// 1. 当modCount != expectedModCount时,会抛出ConcurrentModificationException异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int i = cursor;
// 2.角标越界检测
if (i >= limit)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
// 3.cursor的值自增1
cursor = i + 1;
// 4.对lastRet进行赋值并将当前角标对应的数据元素return掉
return (E) elementData[lastRet = i];
}
public void remove() {
// 1.角标越界检测
if (lastRet < 0)
throw new IllegalStateException();
// 2. 当modCount != expectedModCount时,会抛出ConcurrentModificationException异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
try {
// 3.调用arraylist的remove方法,进行remove操作
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
// 4.重点:在3 处调用arraylist的remove方法,进行remove操作时,会将modCount的值自增1,在这里重新对expectedModCount进行赋值操作,否则会导致modCount != expectedModCount,抛出ConcurrentModificationException异常。
expectedModCount = modCount;
limit--;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
上述就是Itr类的所有源代码,有没有很少。我们可以看到Itr实现了Iterator,主要包含三个常用的方法,分别是:hasNext()、next()、remove()。上述代码中的注释已经写得很清楚了,相信大家都能理解。
关于ArrayList的源码分析到这里就结束了,有什么表述不准确的地方欢迎各位老哥指点哈哈。