内部结构
ArrayList内部实现是一个Object数组,这算是一个常识了;下文中所说数组如无特殊说明,一般是指此数组。
add方法
新增元素前需要先扩容,扩容机制很简单,如果长度够则使用原来的数组,如果数组长度不够则扩大1.5倍,还不够则直接扩大到所需长度 。
特别注意,扩容是把原来数组的内容拷贝到一个新数组中去,这个新数组的长度是扩容后的长度;注意,是新建了一个数组,并不是扩大原来数组的长度。
remove方法
删除一个元素,如下;实际上,源码用的方法也很笨拙。
public E remove(int index) {
//不重要
rangeCheck(index);
//增加修改次数
modCount++;
//获取要被移除的数据
E oldValue = (E) elementData[index];
//计算出要移动的元素个数
int numMoved = size - index - 1;
//拷贝,其实就是用拷贝的方法把要删除的元素后面的元素往前移动一格
//把当前数组的部分数据拷贝到当前数组的某个地方
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
//由于上一步是拷贝,所以需要释放最后一格元素,方便垃圾回收
elementData[--size] = null;
return oldValue;
}
modCount
ArrayList内部会维护一个modCount整型变量,记录数组进行结构性修改的次数。add方法和remove方法都会使该变量加1。
迭代器
增强型for循环会转换成迭代器,或者说背后就是迭代器。所以,如果对象实现了Iterable接口,就可以使用增强型for循环。
在迭代过程中,无法添加、插入、删除元素,因为以上操作会改变数组结构,会导致迭代器维护的索引位置失效。但是可以使用迭代器的remove方法,因为remove方法会更新索引位置相关的数据。其实看看ArrayList的remove方法源码就知道了,该方法并没有针对索引位置数据进行任何操作。
关于迭代器,next()方法底层还是从数组中用get下标的方式获取数据,使用remove方法前要先使用next方法。
直接上源码,如下。
//为了更好理解代码,我们假设size为10
private class Itr implements Iterator<E> {
int cursor; // index of next element to return 下一个元素的下标,初始为0
int lastRet = -1; // index of last element returned; -1 if no such 最后一个返回的元素的下标
int expectedModCount = modCount;//数组结构性修改的次数,modCount进行初始化。
Itr() {}
//判断是否存在下一个元素,size是外部类的对象属性,记录元素个数
public boolean hasNext() {
//代码逻辑很简单,cursor初始为0,调用next方法会让cursor加1,
//只要没到达10,就说明还有下一个元素
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//先检查数组是否发生了结构性变化,如果发生了结构性变化会抛出异常
checkForComodification();
//这些不重要
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
//把cursor加1
cursor = i + 1;
//返回元素,并设置lastRet
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
//调用外部remove方法删除元素
this.remove(lastRet);
/*注意,要先调用迭代器next方法才能调用本方法,所以这时候如果cursor是6,lastRet
应该是5。之所以要把cursor设为5,是因为调用了外部remove方法,之前6-9的元素移
动到了5-8。*/
cursor = lastRet;
//由于元素被删除,所以上一个返回元素下标被设为不存在。
lastRet = -1;
//更新expectedModCount,否则调用迭代器其他方法时会抛出异常
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = java.util.ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = java.util.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;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
如果关于迭代器以上的内容看起来有些困惑,你只需记住以下内容:
迭代器内部维护了一个游标,这个游标指示当前所处位置。如果调用非迭代器内部的方法对数组进行增删,会改变数组结构,数组内的元素可能会移动,游标可能会失效。