想尝试做源码分析好久了,总是想着要不停去学习java新的知识,静不下心来回顾学过的知识。今天终于是开了个头,希望能一直坚持吧! 其实网上关于各种源码分析的文章或者博客已经很多了,很多也写的特别好,但我想着,还是要自己真正的过一遍才能有更深刻的印象和更深入的了解,才能够学的更加扎实。 每天进步一点点!
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
1. 成员变量
private static final Object[] EMPTY_ELEMENTDATA = {};//使用有参构造显式传入0时使用该数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//无参构造器时使用该数组
transient Object[] elementData;//ArrayList内部维护的数组
private int size;//实际元素的个数
2. 构造器
/**
* 构造具有指定初始容量的空list
*
* @param initialCapacity 初始容量
* @throws IllegalArgumentException 参数不合法时抛出异常
*/
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);
}
}
/**
* 构造一个初始容量为0的空列表。
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造一个列表,其中包含指定集合的元素,按集合的迭代器返回元素的顺序排列。
*
* @param c 指定集合
* @throws NullPointerException 指定集合为null时抛出异常
*/
public ArrayList(Collection<? extends E> c) {
//将指定集合转成数组赋值给elementData
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//指定集合长度不为0时
if (elementData.getClass() != Object[].class)//这个操作没看懂...
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//指定集合长度为0,返回空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
3. 成员方法
-
/** * 确认是否需要扩容 * * @param minCapacity 所需的最小容量 */ public void ensureCapacity(int minCapacity) { if (minCapacity > elementData.length && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA && minCapacity <= DEFAULT_CAPACITY)) { /* 扩容条件分析: 1.minCapacity > elementData.length:所需的最小容量大于数组长度 2.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA:使用无参构造器创建的新数组 3.minCapacity <= DEFAULT_CAPACITY:所需的最小容量小于默认容量 满足1 且 (不满足2 或 不满足3) */ modCount++; grow(minCapacity);//扩容 } }
-
/** * 扩容 * 只在add、addAll方法中被调用 * 即只有在添加元素时才会判断是否需要扩容 * * @param minCapacity 所需的最小容量 * @throws OutOfMemoryError 所需的最小容量小于0时抛出异常 */ private Object[] grow(int minCapacity) { int oldCapacity = elementData.length; if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //当前容量大于0或者不是使用无参构造器创建的新数组(???感觉怪怪的...) //生成新的容量,生成方法在下面分析 int newCapacity = ArraysSupport.newLength(oldCapacity,/* 当前容量 */ minCapacity - oldCapacity, /* 所需容量和当前容量差值 */ oldCapacity >> 1 /* 当前容量一半 */); //返回新容量大小的数组 return elementData = Arrays.copyOf(elementData, newCapacity); } else { //数组为空时,新建数组,长度取默认容量和所需容量的较大值 return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)]; } } //供内部调用 private Object[] grow() { return grow(size + 1); } //生成新容量的方法(ArraysSupport类提供的静态方法): public static int newLength(int oldLength, int minGrowth, int prefGrowth) { //增量选择为 所需容量与当前容量差值 和 当前容量一半 中的较大值 int newLength = Math.max(minGrowth, prefGrowth) + oldLength; if (newLength - MAX_ARRAY_LENGTH <= 0) { /* 要分配的最大数组长度(除非必要) 某些虚拟机在数组中保留一些标头字 尝试分配更大的数组可能会导致内存溢出错误 */ /* public static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8; */ //如果新的容量没有超过最大数组长度,返回新的容量 return newLength; } //如果新的容量超过了最大数组长度,调用hugeLength方法 return hugeLength(oldLength, minGrowth); } //hugeLength方法(ArraysSupport类中的静态方法) private static int hugeLength(int oldLength, int minGrowth) { int minLength = oldLength + minGrowth; //所需容量 if (minLength < 0) { // 超过int范围,抛出异常 throw new OutOfMemoryError("Required array length too large"); } if (minLength <= MAX_ARRAY_LENGTH) { //所需容量小于最大数组长度 return MAX_ARRAY_LENGTH; //返回最大数组长度 } return Integer.MAX_VALUE; //否则,返回整型最大值 }
-
//获取元素 public E get(int index) { //检查索引 Objects.checkIndex(index, size); return elementData(index); }
-
//设置元素的值 public E set(int index, E element) { //检查索引 Objects.checkIndex(index, size); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
-
//增加元素 public boolean add(E e) { //修改次数+1 modCount++; //调用下面的方法 add(e, elementData, size); return true; } private void add(E e, Object[] elementData, int s) { if (s == elementData.length) //实际元素的个数和数组长度相等时,扩容 elementData = grow(); //将e加入数组中 elementData[s] = e; //实际元素的个数+1 size = s + 1; } //插入元素 public void add(int index, E element) { //检查索引 rangeCheckForAdd(index); //修改次数+1 modCount++; final int s; Object[] elementData; if ((s = size) == (elementData = this.elementData).length) //实际元素的个数和数组长度相等时,扩容 elementData = grow(); //待插入位置之后的元素后移 System.arraycopy(elementData, index, elementData, index + 1, s - index); elementData[index] = element; size = s + 1; }
-
//移除元素 public E remove(int index) { //检查索引 Objects.checkIndex(index, size); final Object[] es = elementData; @SuppressWarnings("unchecked") E oldValue = (E) es[index]; //调用下面方法 fastRemove(es, index); return oldValue; } private void fastRemove(Object[] es, int i) { //修改次数+1 modCount++; final int newSize; if ((newSize = size - 1) > i) //要移除的元素不是最后一个,后面的元素前移 System.arraycopy(es, i + 1, es, i, newSize - i); //最后一个元素的引用置空 es[size = newSize] = null; }
4. 关于迭代
所有的Collection都实现了Iterable接口,可以使用for-each循环进行迭代
for-each循环非常好用,我们都知道,for-each背后是用迭代器实现的,编译器会将其转换为类似如下代码:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
it.next();
}
也因此在ArrayList中使用for-each循环会遇到一些问题
比如,我们想在遍历的过程中添加某个元素:
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
for (String s : list) {
if ("A".equals(s)) {
list.add("C");
}
}
看起来是比较正常的需求,然而,这段代码会抛出异常:
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1009)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:963)
at com.riven.ArrayListTest.main(ArrayListTest.java:12)
并发修改异常,为什么呢?因为迭代器内部会维护一些索引位置相关的数据,要求在迭代过程中,容器不能发生结构性变化,否则这些索引位置就失效了。所谓结构性变化就是添加、插入和删除元素,只是修改元素内容不算结构性变化。
如何避免异常呢?可以使用迭代器的remove方法,如下所示:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("A")) {
it.remove();
}
}
那么这是什么原理呢?通过观察前面ArrayList的一些常用方法的源码中,在添加、插入和删除方法中,都出现了一个字段modCount
,这个字段继承自AbstractList,表示修改次数,官方文档注释中,对这个字段的解释是:子类可以通过使用这个字段来提供 fail-fast iterators。那么这个fail-fast是怎么实现的呢?我们来看一下ArrayList跟迭代器有关的源码
public Iterator<E> iterator() {
return new Itr();
}
ArrayList通过上面这个方法实现了一个迭代器,其中,Itr是一个内部类,实现了Iterator接口
private class Itr implements Iterator<E>
内部有三个成员变量
int cursor; // 下一个要返回的元素位置
int lastRet = -1; // 最后一个返回的索引位置,如果没有,返回-1
int expectedModCount = modCount; //期望的修改次数,初始化为外部类当前的修改次数modCount
先看一下hasNext()
方法的实现
public boolean hasNext() {
return cursor != size;
}
没什么可说的,主要看一下next()
方法的实现
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
这个方法的第一行,调用了checkForComodification()
方法
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
在这个方法里面,判断了expectedModCount
和modCount
是否相等,如果不相等,便会抛出异常。我们前面说过,ArrayList中的添加、删除、插入等方法会改变modCount
的值,迭代器每一次迭代,都会调用next()
方法,一旦我们调用了使容器发生结构性变化的方法,就会抛出并发修改异常。
那么为什么调用迭代器的remove()
方法就不会抛出异常了呢?我们来看一下remove()
方法
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
在这个方法里面,迭代器调用了ArrayList的remove方法,然后更新了三个成员变量的值,让expectedModCount
等于modCount
,于是,可以正确删除。但是要注意的是,调用remove()
方法之前必须调用next()
方法,因为remove()
方法删除的是lastRet
即最后一个返回的索引位置的元素。
5. 特点
对于ArrayList,它的特点是内部采用动态数组实现,这决定了以下几点。
- 可以随机访问,按照索引位置进行访问效率很高,用算法描述中的术语,效率是O(1),简单说就是可以一步到位。
- 除非数组已排序,否则按照内容查找元素效率比较低,具体是O(N), N为数组内容长度,也就是说,性能与数组长度成正比。
- 添加元素的效率还可以,重新分配和复制数组的开销被平摊了,具体来说,添加N个元素的效率为O(N)。
- 插入和删除元素的效率比较低,因为需要移动元素,具体为O(N)。