ArrayList
List
接口的可变数组实现,实现了所有可选的列表操作,并允许存放所有元素,包括null
。除了实现List
接口之外,ArrayList
类还提供一些方法来操作用于内部存储列表数组的大小。该类和Vector
类大体类似,除了本类是非同步的。
size
、isEmpty
、get
、set
、iterator
和listIterator
操作均在恒定时间内运行。add
操作执行需要均摊恒定时间,也即新增n
个元素需要的时间为O(n)
。所有其他操作都在线性时间内运行(粗略地说)。该实现与LinkedList
实现相比,常量因子较低。
每个ArrayList
实例都有一个容量。容量是用于在列表中存储元素的数组的大小。它总是至少与列表大小一样大。将元素添加到ArrayList
,其容量会自动增长。除了添加元素具有固定的摊销时间成本外,没有指定扩充策略的详细信息。
在添加大量元素之前,应用程序可以使用ensureCapacity
操作先增加ArrayList
实例的容量。这可以减少增量重新分配的数量。
需要注意,本实现是非同步的,如果多个线程同时访问ArrayList
实例,并且至少一个线程修改了列表结构,则它必须通过外部实现同步。(结构上的修改是指添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅设置元素的值不是结构上的修改。)通常,是通过在自然封装列表的某个对象上进行同步来完成此操作。
如果不存在这样的对象,则应使用Collections#synchronizedList
方法“包装”列表。最好在创建列表时完成此操作,以防止意外不同步的访问列表:
List list = Collections.synchronizedList(new ArrayList(...));
此类的iterator()
和listIterator(int)
方法返回的迭代器采用的是“快速失败”机制:如果列表在迭代器创建后的任意时间、以任意方式进行了结构上的改动,除了通过迭代器自己的ListIterator#remove()
或ListIterator#add(Object)
方法外,迭代器都将抛出ConcurrentModificationException
异常。因此,面对并发修改,迭代器会快速干净地失败,而不会在未来的不确定时间面临任意、不确定行为的风险。
请注意,迭代器的“快速失败”行为无法得到保证,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。“快速失败”的迭代器会尽最大努力抛出ConcurrentModificationException
。因此,编写依赖于此异常的程序以确保其正确性是错误的:迭代器的“快速失败”行为应仅用于检测bug。
该类是Java集合框架的成员类。
序列化版本唯一ID。
当前列表的默认初始容量。
用于空实例的共享空数组实例。
共享的空数组实例,用于默认大小的空实例。将它与EMPTY_ELEMENTDATA
区别开来,是为了了解添加第一个元素时要扩充多少。
数组缓冲区,ArrayList
的元素便存储在其中。ArrayList
的容量就是这个数组缓冲区的长度。添加第一个元素时,任何满足elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
的空ArrayList
的容量都将扩展为DEFAULT_CAPACITY
。
该属性的访问修饰符非私有,以简化嵌套类访问。
ArrayList
的大小(包含的元素数量)。
构造一个具有指定初始容量的空列表。
源码如下:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 指定容量大于0,初始化一个指定容量的数组赋值给elementData
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 指定容量等于0,elementData直接取空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 指定容量小于0,抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
构造一个初始容量为10的空列表。
源码如下:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
构造一个包含给定的集合中所有元素的列表,元素插入本列表时按照集合的迭代器返回元素的顺序。
源码如下:
public ArrayList(Collection<? extends E> c) {
// 先将指定集合转换为对象数组
Object[] a = c.toArray();
// 将数组长度赋值给列表的size,然后判断size是否为0
if ((size = a.length) != 0) {
// 如果指定的集合不为空,然后判断该指定集合是否是ArrayList
if (c.getClass() == ArrayList.class) {
// 指定集合是ArrayList,直接把数组a赋值给当前集合的elementData数组
elementData = a;
} else {
// 指定集合不是ArrayList,需要将数组a复制给elementData数组
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// 指定的集合为空,给elementData字段赋值一个空数组
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
至于为什么不能把指定的集合c
转为数组a
之后,把这个数组a
直接赋值给当前的ArrayList
的elementData
呢?
这是由于此处存在Java的一个bug(6260652),也就是c.toArray
可能返回的不是Object[]
类型。如下测试用例所示:
@Test
public void testArrayListWithCollection() {
// list1,类型是Arrays中自已实现的ArrayList,其内部用于存储列表元素的数组类型为:E[],E为泛型
List<Integer> list1 = Arrays.asList(1, 2, 3);
System.out.println(list1.toArray().getClass());
// list2,类型是java.util.ArrayList,即使声明这是一个Integer列表,但其用于存储列表元素的数组类型仍为:Object[]
List<Integer> list2 = new ArrayList<>();
list2.add(1);
System.out.println(list2.toArray().getClass());
// 使用参数为一个集合的构造器构造一个java.util.ArrayList实例,即使该集合的toArray()方法返回的并非Object[]类型,则该构造器也会对其进行转换
List<Integer> list3 = new ArrayList<>(list1);
System.out.println(list3.toArray().getClass());
}
输出:
class [Ljava.lang.Integer;
class [Ljava.lang.Object;
class [Ljava.lang.Object;
将该ArrayList
实例的容量调整为列表当前的大小。应用程序可以使用此操作来最大程度地减少ArrayList
实例的存储。
源码如下:
public void trimToSize() {
// 修改次数加1
modCount++;
// 只有当前列表的元素个数小于数组长度时才执行此操作
if (size < elementData.length) {
// 列表的元素个数为0,则置elementData数组为空数组
// 否则,执行数组复制,将数组中有元素的部分复制出来赋值给elementData数组
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
确保本列表的容量至少可以容纳参数minCapacity
指定的元素个数,如有必要的话会增加此ArrayList
实例的容量。
源码如下:
public void ensureCapacity(int minCapacity) {
// 先计算一个最小扩充值
// 如果当前列表的elementData数组不是默认容量的空数组,则最小扩充值取0
// 否则最小扩充值默认容量,也即10
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
// 如果指定的最小容量大于最小扩充值,则调用ensureExplicitCapacity方法进行精确扩容
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
计算最小容量值。
源码如下:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果指定的元素数组是默认容量空数组,则返回默认容量空数组和指定最小容量这二者的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 如果指定的元素数组已非默认容量空数组,此时直接返回最小容量
return minCapacity;
}
内部使用的(私有的)容量确保方法。
源码如下:
private void ensureCapacityInternal(int minCapacity) {
// 先调用计算容量方法获得最小容量值,再调用ensureExplicitCapacity明确扩容
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
明确的扩容方法,判断若指定的最小容量大于当前元素数组的容量,则对数组进行扩容。
源码如下:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
// 指定的最小容量大于元素数组的长度才进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
可分配的数组的最大大小。一些虚拟机在数组中保留一些头字。尝试分配更大的数组可能会导致 OutOfMemoryError
:请求的数组大小超出虚拟机限制。
增加容量以确保它至少可以容纳由最小容量参数指定的元素数量。
源码如下:
private void grow(int minCapacity) {
// overflow-conscious code
// 取出原容量
int oldCapacity = elementData.length;
// 先计算一个新容量,基本上是扩容1.5倍(其实0.5并不准确,因为右移一位计算时没有小数,小数位全舍弃)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 扩容“1.5”倍后,判断是否能满足最小容量需求
if (newCapacity - minCapacity < 0)
// 仍无法满足最小容量需求,则此时新容量直接取参数中指定的最小容量
newCapacity = minCapacity;
// 判断新容量是否大于最大数组容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 新容量大于最大数组容量,此时调用hugeCapacity来获取新容量
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();
// 如参数指定的最小容量 大于 最大数组容量,则新容量取Integer的最大值
// 否则取最大数组容量
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
之前说MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
,因为有的虚拟机中实现的数组可能会有头字,进而导致数组的最大容量无法达到Integer.MAX_VALUE
,那如果按照本方法中的扩容策略,容量最大还是可以取到Integer.MAX_VALUE
的,那岂不还是会出现未知的风险?
所以,ArrayList的最大容量并非Integer.MAX_VALUE - 8
,而是Integer.MAX_VALUE
。
返回当前列表中元素的个数,即私有属性size
。
源码如下:
public int size() {
return size;
}
判断当前ArrayList
实例的元素数量(即size
)是否为0。
源码如下:
public boolean isEmpty() {
return size == 0;
}
判断当前列表是否包含参数中指定的元素。
如果当前列表包含指定的元素,则返回true
。更正式地说,当且仅当当前列表至少包含一个元素e
可满足(o==null ? e==null : o.equals(e))
时返回true
。 源码如下:
public boolean contains(Object o) {
// 先获取指定元素的索引,然后判断索引是否大于等于0,不大于等于0说明没找到
return indexOf(o) >= 0;
}
返回参数中给定的元素在本列表中第一次出现的索引位置。如果给定的元素在本列表不存在,则返回-1
。更准确的说,返回可满足(o==null ? get(i)==null : o.equals(get(i)))
的最小索引i
,如果该索引不存在的话,返回-1
。
源码如下:
public int indexOf(Object o) {
// 根据指定元素是否为null采用不同的方式处理
// 均是从头开始遍历列表,命中了则返回当前索引
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;
}
// 未找到指定元素的索引,返回-1
return -1;
}
本方法与indexOf
类似,不同之处在于,本方法是从后向前遍历,找到符合条件的最大索引。
返回参数中给定的元素在本列表中最后一次出现的索引位置。如果给定的元素在本列表不存在,则返回-1
。更准确的说,返回可满足(o==null ? get(i)==null : o.equals(get(i)))
的最大索引i
,如果该索引不存在的话,返回-1
。
源码如下:
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;
}
获取一个当前ArrayList
实例的浅复制实例,即ArrayList
实例是新的,但是其中的元素没有被复制,仍是原来的元素。
源码如下:
public Object clone() {
try {
// 直接调用Object类的clone方法进行克隆
ArrayList<?> v = (ArrayList<?>) super.clone();
// 由于Object类的clone方法是浅复制,也即克隆出的新列表虽有新的内存地址,但新列表持有的对象数组仍是老List的对象数组,此时将数组再复制一份复制给新列表
v.elementData = Arrays.copyOf(elementData, size);
// 将新列表的修改次数归0
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
返回一个以正确的顺序(从第一个元素到最后一个元素)包含当前列表中所有元素的数组。 返回的数组将是“安全的”,因为此列表不保留对其的引用。 (换句话说,此方法必须分配一个新数组)。因此,调用者可以自由修改返回的数组。
此方法充当基于数组的API和基于集合的API之间的桥梁。
源码如下:
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
返回一个数组,该数组按正确的顺序包含此列表中的所有元素(从第一个元素到最后一个元素);所返回数组的运行时类型是指定数组的运行时类型。如果列表适合指定的数组,则将其返回。否则,将使用指定数组的运行时类型和此列表的大小分配一个新数组。 如果列表适合指定的数组并有剩余空间(即数组中的元素多于列表),则紧接集合结束后的数组中的元素设置为null
。(如果调用者知道列表不包含任何null
元素,则这对于确定列表的长度很有用。)
源码如下:
public <T> T[] toArray(T[] a) {
if (a.length < size)
// 如果指定的数组a容量不足,则会将对象数组复制到新数组中,新数组的类型就是指定的数组a的类型
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
// a的容量足够,将对象数组复制到其中
System.arraycopy(elementData, 0, a, 0, size);
// 如果对象数组a的长度大于当前列表元素个数,则最后一个元素后面的位置置为null
if (a.length > size)
a[size] = null;
return a;
}
获取指定索引位置的元素。
源码如下:
E elementData(int index) {
// 取出元素后会将其强转为当前列表持有元素的类型,因为对象数组elementData中存的都是Object对象
return (E) elementData[index];
}
返回此列表中指定位置的元素。
源码如下:
public E get(int index) {
// 首先对索引位置进行范围检查
rangeCheck(index);
// 调用elementData方法取出指定位置的数据
return elementData(index);
}
用指定的元素element
替换此列表中指定位置index
的元素。
源码如下:
public E set(int index, E element) {
// 首先对索引进行范围检查
rangeCheck(index);
// 调用elementData方法取出指定位置当前的元素
E oldValue = elementData(index);
// 替换元素数组中指定位置的元素为参数中指定的元素
elementData[index] = element;
// 返回旧值
return oldValue;
}
将指定的元素追加到此列表的末尾,然后返回true
。
源码如下:
public boolean add(E e) {
// 确保容量足够
ensureCapacityInternal(size + 1); // Increments modCount!!
// 设置索引位置为size的元素为参数中指定的元素,然后再将size加1
elementData[size++] = e;
return true;
}
将指定的元素element
插入此列表中的指定位置index
。将当前在该位置的元素(如果有)和任何后续元素右移(将其索引加1)。
源码如下:
public void add(int index, E element) {
// 首先对指定的索引index执行检查,看其是否符合新增的条件(大于0小于等于size)
rangeCheckForAdd(index);
// 确保容量足够
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将指定索引index之后的元素全部右移一位,即各索引加1
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 将指定索引位置index处的元素替换为参数中指定的元素element
elementData[index] = element;
// 将列表元素个数加1
size++;
}
删除此列表中指定位置的元素。将所有后续元素向左移动(将其索引减1)。
源码如下:
public E remove(int index) {
// 对索引进行范围检查
rangeCheck(index);
modCount++;
// 取出指定索引位置当前的元素
E oldValue = elementData(index);
// 计算出要移动的元素个数
int numMoved = size - index - 1;
// 如果需移动的元素个数大于0,则将对象数组从index+1位置及之后的元素复制到index位置及之后
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 先将size减1,然后将最后一个元素置为null,因为该位置的元素已被复制到它的前一位了
elementData[--size] = null; // clear to let GC do its work
// 返回旧值
return oldValue;
}
如果存在指定元素,则从该列表中删除第一次出现的该元素。如果列表不包含该元素,则该列表将不会改变。更准确地说,是删除满足(o==null ? get(i)==null : o.equals(get(i)))
(如果存在这样的元素)的最小索引i
对应的元素。如果此列表包含指定的元素,则返回true
(或者等效地,如果由于本方法被调用而导致此列表有更改,则返回true
)。
源码如下:
public boolean remove(Object o) {
// 根据指定的元素o是否为空走不同的逻辑
if (o == null) {
// 遍历对象数组,逐个判断每个元素是否与指定元素相等,相等则调用fastRemove进行删除
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
// 删除了数据,返回true
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
// 没有删除数据,返回false
return false;
}
私有的删除方法,跳过边界检查并且不返回移除的值。
源码如下:
private void fastRemove(int index) {
modCount++;
// 与根据索引删除元素的方法中类似,先计算出要移动索引的元素个数
int numMoved = size - index - 1;
// 如果需要移动索引,则同样把index+1及后面的元素复制到从index及之后的位置
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将size减1,然后将最后一个元素置空
elementData[--size] = null; // clear to let GC do its work
}
从此列表中删除所有元素。该方法被调用后,该列表将为空。
源码如下:
public void clear() {
modCount++;
// clear to let GC do its work
// 遍历对象数组,将每个位置上的元素置为null
for (int i = 0; i < size; i++)
elementData[i] = null;
// 将size置为0
size = 0;
}
按照参数中指定集合的Iterator
返回的顺序,将指定集合中的所有元素追加到此列表的末尾。 如果在操作进行过程中修改了指定的集合,则此操作的行为是不确定的。(这意味着如果指定的集合就是本列表自身,并且本列表是非空的,则此调用的行为是不确定的。)
源码如下:
public boolean addAll(Collection<? extends E> c) {
// 将指定集合转为数组
Object[] a = c.toArray();
int numNew = a.length;
// 元素插入前的常规操作:确认容量足够
ensureCapacityInternal(size + numNew); // Increments modCount
// 将指定集合转化成的数组a复制到本列表的对象数组中
System.arraycopy(a, 0, elementData, size, numNew);
// 增加size
size += numNew;
// 若此方法实际插入了元素,则返回true,否则返回false
return numNew != 0;
}
从指定位置index
开始,将指定集合c
中的所有元素插入此列表。将当前位于该位置的元素(如果有的话)和任何后续元素向右移动(对它们的索引加一)。 新元素将按照指定集合的迭代器返回的顺序出现在列表中。
源码如下:
public boolean addAll(int index, Collection<? extends E> c) {
// 检查指定索引是否合法(大于0小于等于size)
rangeCheckForAdd(index);
// addAll的常规操作,将指定集合转为数组,然后确保容量足够
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
// 先判断是否要移动索引,如果插入位置不是列表末尾的话,就需要移动
// 此举是为了腾出空来放置指定集合内的元素
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 将指定集合转化成的数组a复制到本列表的对象数组中刚腾出来的位置
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
删除当前列表中所有索引位置在fromIndex
(包含)和toIndex
(不包含)之间的元素。后面的元素都向左异动(减小其索引)。
调用此方法会缩短列表(toIndex - fromIndex)
个元素。如果toIndex==fromIndex
,该操作将不会对列表造成影响。
源码如下:
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
// 计算要移动的元素个数,toIndex及之后位置的元素都需要移动
int numMoved = size - toIndex;
// 将toIndex及之后的元素复制到fromIndex及之后的位置
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
// 拿到列表的新大小
int newSize = size - (toIndex-fromIndex);
// 将索引位置在新大小之后的元素均置为null
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
检查给定的索引是否在范围内。如果不是,则抛出适当的运行时异常。此方法不检查索引是否为负数:它始终在数组访问之前立即使用,如果索引为负数,则抛出 ArrayIndexOutOfBoundsException
。
源码如下:
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
add
和addAll
使用的rangeCheck
版本。
源码如下:
private void rangeCheckForAdd(int index) {
// 该校验允许index等于size,因为在插入元素时,可以往size这个索引位置插入
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
构造一个IndexOutOfBoundsException
详细消息。在错误处理代码的许多可能重构中,这种“概述”在服务器和客户端VM上表现最佳。
源码如下:
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
从此列表中移除指定集合中包含的所有元素。
源码如下:
public boolean removeAll(Collection<?> c) {
// 指定集合c不可为空
Objects.requireNonNull(c);
// 调用batchRemove方法删除元素
return batchRemove(c, false);
}
仅保留此列表中指定集合中包含的元素。换句话说,从该列表中删除所有未包含在指定集合中的元素。
源码如下:
public boolean retainAll(Collection<?> c) {
// 指定集合c必须非空
Objects.requireNonNull(c);
// 调用batchRemove方法,此处与removeAll方法内不同,此处传的第二个参数为true
return batchRemove(c, true);
}
批量删除的方法,参数complement
为补集的意思。也就是,当complement
为true
时,批量删除掉的是补集(即当前列表在指定集合c中的补集),否则批量删除掉的就是集合c
。
源码如下:
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
// r:遍历当前集合时遍历到的索引位置;w:“新”对象数组的size
int r = 0, w = 0;
boolean modified = false;
try {
// 遍历当前集合
for (; r < size; r++)
// 先求出指定的集合c中是否包含当前迭代到的元素,再根据是否是删除补集来判断出是否要保留当前元素
if (c.contains(elementData[r]) == complement)
// 将当前元素从新排入对象数组
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
// 此处,如果r和size不相等,说明在遍历当前集合的过程中抛出了异常,未能遍历完
if (r != size) {
// 此时将遍历停止的位置及之后的元素拼接到对象数组的w位置之后
System.arraycopy(elementData, r,
elementData, w,
size - r);
// “新”对象数组的size加上后面那些没遍历到的元素个数,这就是调用完此方法后当前列表的元素个数了
w += size - r;
}
// 如果“新”对象数组的元素个数与当前列表的元素个数不相等,说明有元素被删除了
if (w != size) {
// clear to let GC do its work
// 遍历对象数组从“新”数组大小之后的元素,对其逐个置为null
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
上述所提到的“新”对象数组,其实数组还是当前列表的对象数组,只不过该方法在删除元素时采用的不是“删除”操作,而是采用的“保留”操作,未被保留的元素相当于被删除了。如果方法判定该元素需保留,则会将它放到对象数组的合适位置,该合适位置是从0开始往后排的,这些位置存放的都是要被保留的元素,这也就是所谓的“新”对象数组。
将ArrayList
实例的状态保存到流中(即序列化它)。该列表的对象数组的长度以及其存放的元素(会以正确的顺序)都会被序列化。
源码如下:
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 size as capacity for behavioural compatibility with clone()
// 写出大小作为与 clone() 的行为兼容性的容量
s.writeInt(size);
// 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
实例(即反序列化它)。
源码如下:
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
// 读取数量和任意隐藏的内容
s.defaultReadObject();
// Read in capacity
// 读取容量
s.readInt(); // ignored
// 如果容量大于0才读取元素
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
// 就像 clone(),根据大小而不是容量分配数组
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
// 以合适的顺序读取所有元素
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
从列表中的指定位置开始,返回此列表列表迭代器(元素以适当的顺序返回)。指定的索引表示初始调用ListIterator.next
将返回的第一个元素。初次调用ListIterator#previous
将返回具有指定索引减一的元素。
源码如下:
public ListIterator<E> listIterator(int index) {
// 对指定索引index进行校验
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
// 返回一个新ListItr实例
return new ListItr(index);
}
如下为一些单元测试:
@Test
public void testListIterator() {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
// 指定索引获取的迭代器,调用next方法会获得该索引位置的元素
System.out.println(list.listIterator(2).previous());
System.out.println(list.listIterator(2).next());
// 不指定索引,等同于指定的索引为0,也即从头开始
System.out.println(list.listIterator().hasPrevious());
System.out.println(list.listIterator().next());
}
输出:
2
3
false
1
返回此列表中的元素的列表迭代器(按适当顺序)。
源码如下:
public ListIterator<E> listIterator() {
return new ListItr(0);
}
返回一个以正确的顺序包含此列表中元素的迭代器。 源码如下:
public Iterator<E> iterator() {
return new Itr();
}
优化版本的AbstractList.Itr
,实现了Iterator
接口。
cursor
意为游标,其用于标识当前迭代器已经执行到哪个元素位置了。所以,它代表的是下一个要返回的元素的index
。
该属性代表的是上一个返回的元素的index
。当迭代器还没开始遍历的时候,其为-1。
期望操作数,用于并发修改检查。
无参构造器。
Itr() {}
用于判断当前迭代器是否还有其他元素。
源码如下:
public boolean hasNext() {
return cursor != size;
}
用于获取迭代器的下一个元素。
源码如下:
@SuppressWarnings("unchecked")
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;
// 将之前记录下的游标位置i赋值给最近一次返回的索引,然后取出该位置的元素并返回
return (E) elementData[lastRet = i];
}
删除当前元素。
源码如下:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
// 调用ArrayList的remove方法删除元素
ArrayList.this.remove(lastRet);
// 将上次返回的元素索引赋值给游标
cursor = lastRet;
// 将最近返回的元素索引置为-1
lastRet = -1;
expectedModCount = modCount;
} 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
// 在迭代结束时更新一次游标和lastRet,以减少堆写入流量
cursor = i;
lastRet = i - 1;
checkForComodification();
}
判断是否存在并发修改操作,若存在则抛出并发修改异常。
源码如下:
final void checkForComodification() {
// 修改次数与期望修改次数不等,说明存在并发修改异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
优化版本的AbstractList.ListItr
,继承了Itr
类并实现了ListIterator
接口。
列表迭代器的构造方法,源码如下:
ListItr(int index) {
super();
// 设置游标位置为指定索引
cursor = index;
}
判断当前迭代器是否还有上一个元素,判断依据是当前游标cursor
是否不等于0,不等于0,说明还有上一个元素,为0说明已到列表的开始位置,没有上一个元素了。
源码如下:
public boolean hasPrevious() {
return cursor != 0;
}
返回下一个元素的索引,其实就是返回当前游标cursor
。
源码如下:
public int nextIndex() {
return cursor;
}
返回上一个元素的索引,其实就是返回当前游标cursor - 1
。
源码如下:
public int previousIndex() {
return cursor - 1;
}
返回上一个元素(非上一个返回的元素)。
源码如下:
@SuppressWarnings("unchecked")
public E previous() {
// 进行并发修改检查
checkForComodification();
// 取出上一个元素的索引位置
int i = cursor - 1;
// 对得到的索引位置进行范围检查
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
// 将当前游标左移一位
cursor = i;
// 将最后返回的索引置为之前算出的索引,然后返回该索引位置的元素
return (E) elementData[lastRet = i];
}
将迭代器最后返回的元素替换为指定的元素e
。
源码如下:
public void set(E e) {
// lastRet小于0说明没有最近返回的元素或者最近返回的元素已被删除
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
在当前游标位置插入指定的元素e
。
源码如下:
public void add(E e) {
checkForComodification();
try {
int i = cursor;
// 在游标位置插入元素e
ArrayList.this.add(i, e);
// 游标右移一位
cursor = i + 1;
// 最近返回的索引置为-1,这个需要特别注意,此处并非置为新插入元素的索引位置
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
返回当前列表在指定的fromIndex
(包含)和toIndex
(不包含)之间部分的视图。(如果fromIndex
和toIndex
相等,则返回的列表为空)返回的列表基于本列表,因此返回列表中的非结构性更改将反映会影响本列表,反之亦然。返回的列表支持列表的所有可选操作。 这种方法消除了显式范围操作(数组中常见的那种操作)的需要。任何期望获得列表的操作都可以用范围操作来代替,通过传递子列表视图而不是整个列表来实现。例如,下面的习惯用法从列表中删除一个元素范围:
list.subList(from, to).clear();
indexOf(Object)
和lastIndexOf(Object)
可以构造类似的习惯用法,并且Collections
类中的所有算法都可以应用于子列表。 如果后备列表(即此列表)以除通过返回列表以外的任何其他方式进行结构化修改,则此方法返回的列表的操作将变得不确定。 (结构修改是指更改此列表的大小的结构修改,或以其他方式扰乱此列表的方式,使得正在进行的迭代可能会产生错误的结果。)
源码如下:
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
示例:
@Test
public void testSubList() {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
List<Integer> subList = list.subList(1, 3);
System.out.println(list);
System.out.println(subList);
System.out.println(subList.getClass());
}
输出:
[1, 2, 3, 4, 5]
[2, 3]
class java.util.ArrayList$SubList
对子列表的范围进行检查。
源码如下:
static void subListRangeCheck(int fromIndex, int toIndex, int size) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > size)
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
}
“子”列表类,更准确地说,其实它是当前ArrayList
的一个视图。
它提供AbstractList
可以提供的一切操作,但是它底层并非是像ArrayList
一样通过elementData
对象数组来存储数据,而是持有它父列表的引用,并且维护一个相对于其父列表的索引偏移量parentOffset
,以及一个相对于其最顶层列表(非SubList
类的实例,而是ArrayList
类的实例)的偏移量offset
。
假设子列表中的某元素在子列表中的索引为subIndex
,则该元素在父列表中的索引为:subIndex + parentOffset
,在其最顶层的ArrayList中的索引为:subIndex + offset
。
当前子列表的父列表。
当前子列表的父列表在其父列表中的偏移量。
当前子列表在其最顶层的ArrayList
列表中的偏移量。
假设存在ArrayList
的实例A
:["零", "一", "二", "三", "四", "五"]
,
存在A
的子列表SubList
的实例S1
:["二", "三", "四"]
,”二
“这个元素在该S1
中的索引为0
,在其父列表A
中的索引为2
,此时S1
的parentOffset
为2
,同时S1
的offset
也为2
。
再假设S1
也存在子列表SubList
的实例S2
:["三", "四"]
,”四
“这个元素在S2
中的索引为1
,在其父列表S1
中的索引为2
,在其最顶层的ArrayList
列表A
中的索引为4
,此时S2
的parentOffset
为1
(2 - 1
),offset
为3
(4 - 1
)。
当前子列表的大小。
SubList
类唯一的构造函数。
源码如下:
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
// 执行各种初始化
this.parent = parent;
// 将fromIndex置为当前子列表相对于其父列表的偏移量
this.parentOffset = fromIndex;
// 将当前子列表相对于其最顶级列表的偏移量加上当前子列表相对于其父列表的偏移量
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
设置当前子列表指定索引位置的元素为指定的元素e
。
源码如下:
public E set(int index, E e) {
// 首先对指定索引index进行检查
rangeCheck(index);
// 检查是否存在并发修改
checkForComodification();
// 首先计算出当前子列表中的index位置转换为其父列表的index(即当前指定索引加上偏移量)
// 调用父类的elementData方法取出对应位置的元素
E oldValue = ArrayList.this.elementData(offset + index);
// 将父类对应位置的元素替换为指定的元素e
ArrayList.this.elementData[offset + index] = e;
// 返回旧元素
return oldValue;
}
获取指定索引位置的元素。
源码如下:
public E get(int index) {
// 首先进行范围检查
rangeCheck(index);
// 进行并发修改检查
checkForComodification();
// 通过指定索引index和偏移量计算出目标元素在父列表中的位置,然后从父列表中将其取出并返回
return ArrayList.this.elementData(offset + index);
}
获取当前子列表的大小。
源码如下:
public int size() {
// 并发修改检查
checkForComodification();
// 返回当前子列表大小
return this.size;
}
向指定索引位置插入指定元素。
源码如下:
public void add(int index, E e) {
// 校验指定索引是否满足新增的要求
rangeCheckForAdd(index);
// 并发修改校验
checkForComodification();
// 此处是调用的父类的add方法,而非最顶层的ArrayList的add方法
// 所以计算索引时,要使用parentOffset,而非offset
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
// 列表大小加1
this.size++;
}
删除指定索引位置的元素,并将该元素返回。
源码如下:
public E remove(int index) {
rangeCheck(index);
checkForComodification();
// 与add一样,删除也是调用父类的方法,所以计算索引时要使用parentOffset
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
删除索引在指定返回范围fromIndex
(包含)和toIndex
(不包含)之间的元素。
protected void removeRange(int fromIndex, int toIndex) {
checkForComodification();
// 也是调用父类的范围删除方法来执行删除,所以使用parentOffset计算索引
parent.removeRange(parentOffset + fromIndex,
parentOffset + toIndex);
this.modCount = parent.modCount;
this.size -= toIndex - fromIndex;
}
将指定集合内的元素添加到当前子列表的末尾。
源码如下:
public boolean addAll(Collection<? extends E> c) {
// 调用addAll方法,指定插入位置的索引为当前列表的size大小处,也即紧接着列表的末尾后面
return addAll(this.size, c);
}
将指定集合内的元素添加到当前子列表的指定索引处。
源码如下:
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
int cSize = c.size();
// 指定的列表没有元素,直接返回false
if (cSize==0)
return false;
checkForComodification();
// 调用父类的插入方法
parent.addAll(parentOffset + index, c);
this.modCount = parent.modCount;
this.size += cSize;
return true;
}
获取迭代器。
源码如下:
public Iterator<E> iterator() {
// 调用AbstractList中的listIterator()方法获取迭代器
return listIterator();
}
获取从指定位置开始的迭代器。
源码如下:
public ListIterator<E> listIterator(final int index) {
checkForComodification();
// 此处借用了插入元素时的索引校验方法,即允许指定的索引为列表的size
rangeCheckForAdd(index);
final int offset = this.offset;
// 此处返回一个ListIterator接口的匿名内部类的实例
return new ListIterator<E>() {
// 游标等于指定索引
int cursor = index;
int lastRet = -1;
int expectedModCount = ArrayList.this.modCount;
// 判断是否还有下一个元素
public boolean hasNext() {
// 判断当前游标是否等于当前子列表的大小
return cursor != SubList.this.size;
}
// 获取下一个元素
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= SubList.this.size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
// 计算元素在顶层的ArrayList列表内的索引,若大于等于数组长度,则报错
if (offset + i >= elementData.length)
throw new ConcurrentModificationException();
// 游标右移一位
cursor = i + 1;
// 将原游标位置赋值给最近返回的元素索引
// 计算出要取出的元素在其顶层的ArrayList中的索引位置
// 通过顶层列表的对象数组elementData获取对应索引位置的元素
return (E) elementData[offset + (lastRet = i)];
}
// 判断前面是否还有元素
public boolean hasPrevious() {
// 判断当前游标是否为0
return cursor != 0;
}
// 获取前面一个元素
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
// 计算出前一个元素在当前子列表中的索引位置
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
// 计算出前一个元素在顶层的ArrayList中的索引位置,并校验
if (offset + i >= elementData.length)
throw new ConcurrentModificationException();
// 这一步相当于是游标前移一位
cursor = i;
// 将最近返回的索引位置置为前一个元素,并返回该元素
return (E) elementData[offset + (lastRet = i)];
}
// 对剩下未遍历到的元素执行给定的操作
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = SubList.this.size;
int i = cursor;
// 游标已在子列表末尾或之后,则说明已无未迭代到的元素,直接返回
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
// 计算出当前游标在顶层ArrayList中的索引位置,并校验
if (offset + i >= elementData.length) {
throw new ConcurrentModificationException();
}
// 从游标位置开始往后遍历,直到当前子列表的末尾,对每个元素执行给定的操作
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[offset + (i++)]);
}
// update once at end of iteration to reduce heap write traffic
// 最近返回索引和当前游标均一次性更新至子列表末尾
lastRet = cursor = i;
checkForComodification();
}
// 获取下一个元素索引
public int nextIndex() {
// 返回当前游标
return cursor;
}
// 获取上一个元素索引
public int previousIndex() {
// 返回当前游标之前的位置
return cursor - 1;
}
// 删除迭代器当前迭代到的元素
public void remove() {
// 最近返回元素小于0,将抛出异常
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
// 调用子列表的remove方法删除对应元素
SubList.this.remove(lastRet);
// 将游标置于最近返回的元素索引位置,相当于左移一位
cursor = lastRet;
// 将最近返回的索引位置置为-1
lastRet = -1;
expectedModCount = ArrayList.this.modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
// 替换最近返回的索引位置的元素为指定的元素
public void set(E e) {
// 最近返回的索引位置不可小于0,否则可能是迭代器还未获取过元素,或者已经进行了元素的增加(add)或删除(remove)
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
// 计算出元素在顶层ArrayList中的索引位置,用ArrayList的set方法替换元素
ArrayList.this.set(offset + lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
// 在当前游标位置插入指定元素
public void add(E e) {
checkForComodification();
try {
int i = cursor;
// 调用子列表的add方法,在当前游标位置插入指定元素
SubList.this.add(i, e);
// 将游标右移一位
cursor = i + 1;
// 将最近返回的索引置为-1
lastRet = -1;
expectedModCount = ArrayList.this.modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
// 检查是否存在并发修改
final void checkForComodification() {
// 如果子列表的期望修改次数不等于ArrayList修改次数,说明存在并发修改
if (expectedModCount != ArrayList.this.modCount)
throw new ConcurrentModificationException();
}
};
}
获取当前SubList
实例的子列表。
源码如下:
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
// 看,此处传入的offset为当前子列表的offset,而非ArrayList的subList方法中传入的0
return new SubList(this, offset, fromIndex, toIndex);
}
对指定索引进行合法性检查。
源码如下:
private void rangeCheck(int index) {
// 指定的索引必须大于0且小于size,否则将造成数据越界
if (index < 0 || index >= this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
在列表插入元素时,对插入位置进行校验。
源码如下:
private void rangeCheckForAdd(int index) {
// 相较于rangeCheck,本校验方法允许指定索引等于size,因为可以往size位置插入元素
if (index < 0 || index > this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
获取数组越界异常的提示信息。
源码如下:
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+this.size;
}
检查列表是否存在并发修改。
源码如下:
private void checkForComodification() {
// 判断ArrayList的修改次数与当前子列表的修改次数是否一致,否则抛出并发修改异常
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
获取当前子列表的拆分器。
源码如下:
public Spliterator<E> spliterator() {
checkForComodification();
// 注意,此处传入的是ArrayList,而非当前子列表
return new ArrayListSpliterator<E>(ArrayList.this, offset,
offset + this.size, this.modCount);
}
遍历当前列表,对每个元素执行给定的操作,直到每个元素都被迭代到或者中间出了异常。
源码如下:
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
// 取出当前列表的对象数组,待会就遍历它
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
// 遍历时每次循环都校验是否存在并发修改,存在则直接结束遍历
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
// 若存在并发修改,直接抛出异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
在此列表中的元素上创建一个“后期绑定”和“快速失败”的Spliterator
。Spliterator
报告Spliterator#SIZED
、Spliterator#SUBSIZED
和Spliterator#ORDERED
。覆盖本实现应该记录其他特征值的描述。
源码如下:
@Override
public Spliterator<E> spliterator() {
return new ArrayListSpliterator<>(this, 0, -1, 0);
}
基于索引一分为二、延迟初始化的Spliterator
。
如果ArrayList
是不可变的或结构上不可变的(没有添加,删除等),我们可以使用Arrays.spliterator
实现它们的分隔器。相反,我们在不牺牲太多性能的情况下,在遍历过程中检测到尽可能多的干扰。我们主要依靠modCounts,这不能保证检测到并发冲突,并且有时对线程内干扰过于保守,但是可以检测到足够的问题,值得在实践中使用。为了实现这一点:
- 我们延迟初始化
fence
和expectedModCount
,直到需要提交给正在检查状态的最新点,从而提高精度。 (这不适用于SubList
,因为SubList
会创建带有当前非惰性值的Spliterator
)。 - 我们仅在
forEach
(对性能最敏感的方法)结束时执行一次ConcurrentModificationException
检查。当使用forEach
(而不是迭代器)时,我们通常只能在动作之后检测干扰,而不是之前。进一步的 CME 触发检查适用于所有其他可能不符合预期的情况,例如null
或给定size()
的太小的elementData
数组,这可能仅由于干扰而发生。这允许forEach
的内部循环在没有任何进一步检查的情况下运行,并简化了 lambda 解析。虽然这确实需要一些检查,但请注意,在list.stream().forEach(a)
的常见情况下,除了forEach
本身之外,不会在任何地方进行检查或其他计算。其他不太常用的方法无法利用这些精简中的大部分。
该拆分器所属的list
。
当前索引,在前进(advance
)或拆分(split
)时修改。
直到使用前都是-1,然后直到最后一个索引。
期望修改次数,设置fence
时初始化。
创建覆盖给定范围的新拆分器。
源码如下:
ArrayListSpliterator(ArrayList<E> list, int origin, int fence,
int expectedModCount) {
// 参数中的list可以为空,除非要遍历它
this.list = list; // OK if null unless traversed
// 属性值初始化
this.index = origin;
this.fence = fence;
this.expectedModCount = expectedModCount;
}
第一次使用时,初始化围栏fence
大小。
源码如下:
private int getFence() { // initialize fence to size on first use
int hi; // (a specialized variant appears in method forEach)
ArrayList<E> lst;
// 将fence大于等于0,直接返回该值
if ((hi = fence) < 0) {
// 如果list为空,则fence初始化为0,并最终返回0
if ((lst = list) == null)
hi = fence = 0;
else {
// 如果list不为空
// 初始化拆分器的期望修改次数
expectedModCount = lst.modCount;
// 将fence初始化为列表的大小
hi = fence = lst.size;
}
}
return hi;
}
执行拆分。
源码如下:
public ArrayListSpliterator<E> trySplit() {
// hi = getFence(),这一步是获取围栏位置
// lo = index,这一步是获取当前索引
// mid = (lo + hi) >>> 1,这一步很关键,求取当前迭代到的位置直到围栏位置的中间索引位置,将用于拆分出新拆分器
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
// 除非太小,否则将范围分成两半
return (lo >= mid) ? null : // divide range in half unless too small
// 拆分出去的新拆分器,是从当前迭代到的位置至上面求出的中间位置
// 此处还有一个不明显但很关键的操作:index = mid,这一步是将当前老拆分器的迭代位置置为上面求出的中间位置
// 这样老拆分器就一分为二了,新拆分器负责前半部分,老拆分器负责后半部分
new ArrayListSpliterator<E>(list, lo, index = mid,
expectedModCount);
}
例如:
存在一个ArrayList
,其size
为35
,然后其调用spliterator()
获取到一个拆分器,此时该拆分器的fence
为-1
,index
为0
。
若此时对该拆分器执行拆分(trySplit()
)操作: 首先调用getFence()
获取围栏大小,此方法中会对fence
做初始化,最终拿到的fence
是35
,在加上当前索引为0
,则计算出的中间值为17((35 + 0) >>> 1 = 17
)。 当前索引为0
,小于中间值,此时可以进行拆分,此时方法返回的新拆分器实例的属性分别为:index
为0
,fence
为17
。同时,原拆分器的index
更新为17
。 此时就相当于一个持有35
个元素的拆分器,拆分出去了一个持有17
个元素的拆分器,自己还剩18
个元素。(所谓“持有”,其实这两个拆分器都是持有该拆分器所属的list
,但通过围栏字段fence
区分出每个拆分器所负责的那一部分)
如图所示:
对当前拆分器迭代到的元素执行给定的操作action
。
源码如下:
public boolean tryAdvance(Consumer<? super E> action) {
if (action == null)
throw new NullPointerException();
// 获取围栏位置以及当前索引
int hi = getFence(), i = index;
// 如果当前索引大于等于围栏位置,说明所有元素均已遍历完,此时没有元素执行给定的操作
if (i < hi) {
// 将索引右移一位
index = i + 1;
// 取出右移前索引位置的元素
@SuppressWarnings("unchecked") E e = (E)list.elementData[i];
// 对取出的元素执行给定的操作
action.accept(e);
// 校验是否存在并发修改
if (list.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
return false;
}
对当前拆分器剩余未迭代到的元素执行给定的操作action
。所谓剩余是指:从当前索引位置到围栏位置之间的元素。
源码如下:
public void forEachRemaining(Consumer<? super E> action) {
int i, hi, mc; // hoist accesses and checks from loop
ArrayList<E> lst; Object[] a;
if (action == null)
throw new NullPointerException();
// 列表必须不能为空,否则抛出并发修改异常
if ((lst = list) != null && (a = lst.elementData) != null) {
// 如果围栏位置小于0,可能是刚基于列表创建的拆分器
if ((hi = fence) < 0) {
mc = lst.modCount;
hi = lst.size;
}
else
mc = expectedModCount;
// 如果当前索引位置大于等于0 且 围栏位置小于等于列表中对象数组的长度,则迭代数组
// 此处的隐秘操作:index = hi,这一步将拆分器的当前索引推至围栏位置了
if ((i = index) >= 0 && (index = hi) <= a.length) {
for (; i < hi; ++i) {
@SuppressWarnings("unchecked") E e = (E) a[i];
action.accept(e);
}
if (lst.modCount == mc)
return;
}
}
throw new ConcurrentModificationException();
}
获取当前拆分器还未进行操作的元素数量。
源码如下:
public long estimateSize() {
// 所有获取围栏位置,然后减去当前索引
return (long) (getFence() - index);
}
该方法是返回当前拆分器的特征值,就ArrayListSpliterator
而言,其特征值有:Spliterator.ORDERED
、Spliterator.SIZED
、Spliterator.SUBSIZED
。
源码如下:
public int characteristics() {
return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
}
参数为一个断言,该方法即对当前列表的每个元素执行该断言的test
方法,命中的则在当前列表中删除,最后返回的是是否删除了本列表的元素。
源码如下:
@Override
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
// figure out which elements are to be removed
// any exception thrown from the filter predicate at this stage
// will leave the collection unmodified
// 找出要删除哪些元素,在此阶段从断言中抛出的任何异常都不会使集合改变
int removeCount = 0;
// 声明一个BitSet,用于存储要删除元素的索引
final BitSet removeSet = new BitSet(size);
final int expectedModCount = modCount;
final int size = this.size;
// 从头开始遍历列表,先找出符合删除条件的元素索引
for (int i=0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
// 取出当前迭代到的元素
final E element = (E) elementData[i];
// 对取出的元素执行给定的测试
if (filter.test(element)) {
// 若满足条件,将该索引记录到removeSet,并将删除数量加1
removeSet.set(i);
removeCount++;
}
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
// shift surviving elements left over the spaces left by removed elements
// 将剩下的元素移动到已删除元素留下的空间上
final boolean anyToRemove = removeCount > 0;
// 上面找到了要删除的元素才执行下面的操作
if (anyToRemove) {
// 计算出新的列表大小
final int newSize = size - removeCount;
// 遍历列表,直到新的列表大小位置处
for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
// 找到下一个未删除的元素索引
i = removeSet.nextClearBit(i);
// 将该索引位置的元素复制到j位置
elementData[j] = elementData[i];
}
// 将新列表尾部的元素都置为null(这些元素都已被复制到前面,覆盖了前面被删掉的元素)
for (int k=newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work
}
// 将新列表的大小赋值给当前列表大小
this.size = newSize;
// 执行并发修改检查
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
return anyToRemove;
}
如下为示例:
@Test
public void testRemoveIf() {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.removeIf(l -> l >1 && l < 4);
System.out.println(list);
}
输出:
[1, 4, 5]
对列表中每个元素执行参数中指定的操作,并使用此操作获得的值代替当前位置上的元素。
源码如下:
@Override
@SuppressWarnings("unchecked")
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final int expectedModCount = modCount;
final int size = this.size;
// 遍历当前列表
for (int i=0; modCount == expectedModCount && i < size; i++) {
// 对每个元素执行给定的操作,使用计算出的结果替代当前元素
elementData[i] = operator.apply((E) elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
如下所示:
@Test
public void testReplaceAll() {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.replaceAll(i -> i * 5);
System.out.println(list);
}
输出:
[5, 10, 15, 20, 25]
基于参数中指定的比较器,对当前列表进行排序。
源码如下:
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
// 使用Arrays的sort方法对当前列表的元素数组执行排序
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
如下所示:
@Test
public void testSort() {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.sort(Comparator.comparingInt(i -> -i));
System.out.println(list);
}
输出:
[5, 4, 3, 2, 1]