code-learning/jdk/03-JDK 源码解析-集合(一)数组 ArrayList.md

1471 lines
54 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 精尽 JDK 源码解析 —— 集合(一)数组 ArrayList
# 1. 概述
ArrayList ,基于 `[]` 数组实现的,支持**自动扩容**的动态数组。相比数组来说,因为其支持**自动扩容**的特性,成为我们日常开发中,最常用的集合类,没有之一。
在前些年,实习或初级工程师的面试,可能最爱问的就是 ArrayList 和 LinkedList 的区别与使用场景。不过貌似,现在问的已经不多了,因为现在信息非常发达,这种常规面试题已经无法区分能力了。当然即使如此,也不妨碍我们拿它开刀,毕竟是咱的“老朋友”。
# 2. 类图
ArrayList 实现的接口、继承的抽象类,如下图所示:[![类图](03-JDK 源码解析-集合(一)数组 ArrayList.assets/01.png)](http://static.iocoder.cn/images/JDK/2019_12_01/01.png)类图
实现了 4 个接口,分别是:
- [`java.util.List`](https://github.com/YunaiV/openjdk/blob/master/src/java.base/share/classes/java/util/List.java) 接口,提供数组的添加、删除、修改、迭代遍历等操作。
- [`java.util.RandomAccess`](https://github.com/YunaiV/openjdk/blob/master/src/java.base/share/classes/java/util/RandomAccess.java) 接口,表示 ArrayList 支持**快速**的随机访问。
> 关于 RandomAccess **标记**接口,我们这里先不展开,胖友可以自行阅读 [《RandomAccess 这个空架子有何用?》](https://juejin.im/post/5a26134af265da43085de060) 文章。
- [`java.io.Serializable`](https://github.com/YunaiV/openjdk/blob/master/src/java.base/share/classes/java/io/Serializable.java) 接口,表示 ArrayList 支持序列化的功能。
> 关于 Serializable **标记**接口,我们这里先不展开,胖友可以自行阅读 [《Java 序列化与反序列化》](https://www.jianshu.com/p/e554c787c286) 文章。
- [`java.lang.Cloneable`](https://github.com/YunaiV/openjdk/blob/master/src/java.base/share/classes/java/lang/Cloneable.java) 接口,表示 ArrayList 支持克隆。
> 关于 Cloneable **标记**接口,我们这里先不展开,胖友可以自行阅读 [《在 Java 中为什么实现了 Cloneable 接口,就能调用 Object 的 clone 方法?》](https://www.zhihu.com/question/52490586/answer/130786763) 讨论,特别是 R 大的回答。
继承了 [`java.util.AbstractList`](https://github.com/YunaiV/openjdk/blob/master/src/java.base/share/classes/java/util/AbstractList.java) 抽象类,而 AbstractList 提供了 List 接口的骨架实现,大幅度的减少了实现**迭代遍历**相关操作的代码。可能这样表述有点抽象,胖友点到 [`java.util.AbstractList`](https://github.com/YunaiV/openjdk/blob/master/src/java.base/share/classes/java/util/AbstractList.java) 抽象类中看看,例如说 `#iterator()``#indexOf(Object o)` 等方法。
😈 不过实际上在下面中我们会看到ArrayList 大量重写了 AbstractList 提供的方法实现。所以AbstractList 对于 ArrayList 意义不大,更多的是 AbstractList 其它子类享受了这个福利。
# 3. 属性
ArrayList 的属性很少,仅仅 **2** 个。如下图所示:
[![ArrayList](03-JDK 源码解析-集合(一)数组 ArrayList.assets/02.png)](http://static.iocoder.cn/images/JDK/2019_12_01/02.png)ArrayList
- `elementData` 属性:元素数组。其中,图中红色空格代表我们已经添加元素,白色空格代表我们并未使用。
- `size` 属性:数组大小。注意,`size` 代表的是 ArrayList 已使用 `elementData` 的元素的数量,对于开发者看到的 `#size()` 也是该大小。并且,当我们添加新的元素时,恰好其就是元素添加到 `elementData` 的位置(下标)。当然,我们知道 ArrayList **真正**的大小是 `elementData` 的大小。
对应代码如下:
```
// ArrayList.java
/**
* 元素数组。
*
* 当添加新的元素时,如果该数组不够,会创建新数组,并将原数组的元素拷贝到新数组。之后,将该变量指向新数组。
*
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access 不使用 private 修复,方便内嵌类的访问。
/**
* 已使用的数组大小
*
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
```
# 4. 构造方法
ArrayList 一共有三个构造方法,我们分别来看看。
**`#ArrayList(int initialCapacity)`**
`#ArrayList(int initialCapacity)` 构造方法,根据传入的初始化容量,创建 ArrayList 数组。如果我们在使用时,如果预先指到数组大小,一定要使用该构造方法,可以避免数组扩容提升性能,同时也是合理使用内存。代码如下:
```
// ArrayList.java
/**
* 共享的空数组对象。
*
* 在 {@link #ArrayList(int)} 或 {@link #ArrayList(Collection)} 构造方法中,
* 如果传入的初始化大小或者集合大小为 0 时,将 {@link #elementData} 指向它。
*
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
public ArrayList(int initialCapacity) {
// 初始化容量大于 0 时,创建 Object 数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
// 初始化容量等于 0 时,使用 EMPTY_ELEMENTDATA 对象
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
// 初始化容量小于 0 时,抛出 IllegalArgumentException 异常
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
```
- 比较特殊的是,如果初始化容量为 0 时,使用 `EMPTY_ELEMENTDATA` 空数组。在添加元素的时候,会进行扩容创建需要的数组。
**`#ArrayList(Collection<? extends E> c)`**
`#ArrayList(Collection<? extends E> c)` 构造方法,使用传入的 `c` 集合,作为 ArrayList 的 `elementData` 。代码如下:
```
// ArrayList.java
public ArrayList(Collection<? extends E> c) {
// 将 c 转换成 Object 数组
elementData = c.toArray();
// 如果数组大小大于 0
if ((size = elementData.length) != 0) {
// defend against c.toArray (incorrectly) not returning Object[]
// (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
// <X> 如果集合元素不是 Object[] 类型,则会创建新的 Object[] 数组,并将 elementData 赋值到其中,最后赋值给 elementData 。
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
// 如果数组大小等于 0 ,则使用 EMPTY_ELEMENTDATA 。
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
```
- 比较让人费解的是,在 `<X>` 处的代码。它是用于解决 [JDK-6260652](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652) 的 Bug 。它在 JDK9 中被解决,😈 也就是说JDK8 还会存在该问题。
我们来看一段能够触发 [JDK-6260652](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652) 的测试代码,然后分别在 JDK8 和 JDK13 下执行。代码如下:
```
// ArrayListTest.java
public static void test02() {
List<Integer> list = Arrays.asList(1, 2, 3);
Object[] array = list.toArray(); // JDK8 返回 Integer[] 数组JDK9+ 返回 Object[] 数组。
System.out.println("array className " + array.getClass().getSimpleName());
// 此处,在 JDK8 和 JDK9+ 表现不同,前者会报 ArrayStoreException 异常,后者不会。
array[0] = new Object();
}
```
- JDK8 执行如下图所示:[![img](03-JDK 源码解析-集合(一)数组 ArrayList.assets/03.png)](http://static.iocoder.cn/images/JDK/2019_12_01/03.png)
- JDK13 执行如下图所示:[![img](03-JDK 源码解析-集合(一)数组 ArrayList.assets/04.png)](http://static.iocoder.cn/images/JDK/2019_12_01/04.png)
- 在 JDK8 中,返回的实际是 `Integer []` 数组,那么我们将 Object 对象设置到其中,肯定是会报错的。具体怎么修复的,看 [JDK-6260652](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652) 的最末尾一段。
**`#ArrayList()`**
无参数构造方法 `#ArrayList()` 构造方法,也是我们使用最多的构造方法。代码如下:
```
// ArrayList.java
/**
* 默认初始化容量
*
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 共享的空数组对象,用于 {@link #ArrayList()} 构造方法。
*
* 通过使用该静态变量,和 {@link #EMPTY_ELEMENTDATA} 区分开来,在第一次添加元素时。
*
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
```
- 在我们学习 ArrayList 的时候一直被灌输了一个概念在未设置初始化容量时ArrayList 默认大小为 10 。但是此处,我们可以看到初始化为 `DEFAULTCAPACITY_EMPTY_ELEMENTDATA` 这个空数组。这是为什么呢ArrayList 考虑到节省内存,一些使用场景下仅仅是创建了 ArrayList 对象实际并未使用。所以ArrayList 优化成初始化是个空数组,在首次添加元素时,才真正初始化为容量为 10 的数组。
- 那么为什么单独声明了 `DEFAULTCAPACITY_EMPTY_ELEMENTDATA` 空数组,而不直接使用 `EMPTY_ELEMENTDATA` 呢?在下文中,我们会看到 `DEFAULTCAPACITY_EMPTY_ELEMENTDATA` 首次扩容为 10 ,而 `EMPTY_ELEMENTDATA` 按照 **1.5 倍**扩容从 0 开始而不是 10 。😈 两者的起点不同,嘿嘿。
# 5. 添加单个元素
`#add(E e)` 方法,**顺序**添加单个元素到数组。代码如下:
```
// ArrayList.java
@Override
public boolean add(E e) {
// <1> 增加数组修改次数
modCount++;
// 添加元素
add(e, elementData, size);
// 返回添加成功
return true;
}
private void add(E e, Object[] elementData, int s) {
// <2> 如果容量不够,进行扩容
if (s == elementData.length)
elementData = grow();
// <3> 设置到末尾
elementData[s] = e;
// <4> 数量大小加一
size = s + 1;
}
```
- `<1>` 处,增加数组修改次数 `modCount` 。在父类 AbstractList 上,定义了 `modCount` 属性,用于记录数组修改次数。
- `<2>` 处,如果元素添加的位置就超过末尾(数组下标是从 0 开始,而数组大小比最大下标大 1说明数组容量不够需要进行扩容那么就需要调用 `#grow()` 方法,进行扩容。稍后我们在 [「6. 数组扩容」](https://svip.iocoder.cn/JDK/Collection-ArrayList/#) 小节来讲。
- `<3>` 处,设置到末尾。
- `<4>` 处,数量大小加一。
总体流程上来说,抛开扩容功能,和我们日常往 `[]` 数组里添加元素是一样的。
看懂这个方法后,胖友自己来看看 `#add(int index, E element)` 方法,**插入**单个元素到指定位置。代码如下:
```
// ArrayList.java
public void add(int index, E element) {
// 校验位置是否在数组范围内
rangeCheckForAdd(index);
// 增加数组修改次数
modCount++;
// 如果数组大小不够,进行扩容
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
// 将 index + 1 位置开始的元素,进行往后挪
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
// 设置到指定位置
elementData[index] = element;
// 数组大小加一
size = s + 1;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
```
# 6. 数组扩容
`#grow()` 方法,扩容数组,并返回它。整个的扩容过程,首先创建一个新的更大的数组,一般是 **1.5 倍**大小(为什么说是一般呢,稍后我们会看到,会有一些小细节),然后将原数组复制到新数组中,最后返回新数组。代码如下:
```
// ArrayList.java
private Object[] grow() {
// <1>
return grow(size + 1);
}
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
// <2> 如果原容量大于 0 ,或者数组不是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时,计算新的数组大小,并创建扩容
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
// <3> 如果是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 数组,直接创建新的数组即可。
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
```
- `<1>` 处,调用 `#grow(int minCapacity)` 方法,要求扩容后**至少**比原有大 1 。因为是最小扩容的要求,实际是允许比它大。
- ```
<2>
```
处,如果原容量大于 0 时,又或者数组不是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时,则计算新的数组大小,并创建扩容。
- `ArraysSupport#newLength(int oldLength, int minGrowth, int prefGrowth)` 方法,计算新的数组大小。简单来说,结果就是 `Math.max(minGrowth, prefGrowth) + oldLength` ,按照 `minGrowth` 和 `prefGrowth` 取大的。
- 一般情况下,从 `oldCapacity >> 1` 可以看处,是 **1.5 倍**扩容。但是会有两个特殊情况1初始化数组要求大小为 0 的时候,`0 >> 1` 时(`>> 1 为右移操作,相当于除以 2`)还是 0 ,此时使用 `minCapacity` 传入的 1 。2在下文中我们会看到添加多个元素此时传入的 `minCapacity` 不再仅仅加 1 ,而是扩容到 `elementData` 数组恰好可以添加下多个元素,而该数量可能会超过当前 ArrayList **0.5** 倍的容量。
- `<3>` 处,如果是 `DEFAULTCAPACITY_EMPTY_ELEMENTDATA` 数组,直接创建新的数组即可。思考下,如果无参构造方法使用 `EMPTY_ELEMENTDATA` 的话,无法实现该效果了。
既然有数组扩容方法,那么是否有缩容方法呢?在 `#trimToSize()` 方法中,会创建大小恰好够用的新数组,并将原数组复制到其中。代码如下:
```
// ArrayList.java
public void trimToSize() {
// 增加修改次数
modCount++;
// 如果有多余的空间,则进行缩容
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA // 大小为 0 时,直接使用 EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size); // 大小大于 0 ,则创建大小为 size 的新数组,将原数组复制到其中。
}
}
```
同时,提供 `#ensureCapacity(int minCapacity)` 方法,保证 `elementData` 数组容量至少有 `minCapacity` 。代码如下:
```
// ArrayList.java
public void ensureCapacity(int minCapacity) {
if (minCapacity > elementData.length // 如果 minCapacity 大于数组的容量
&& !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
&& minCapacity <= DEFAULT_CAPACITY)) { // 如果 elementData 是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的时候,
// 需要最低 minCapacity 容量大于 DEFAULT_CAPACITY ,因为实际上容量是 DEFAULT_CAPACITY 。
// 数组修改次数加一
modCount++;
// 扩容
grow(minCapacity);
}
}
```
- 比较简单,我们可以将这个方法理解成主动扩容。
# 7. 添加多个元素
`#addAll(Collection<? extends E> c)` 方法,批量添加多个元素。在我们明确知道会添加多个元素时,推荐使用该该方法而不是添加单个元素,避免可能多次扩容。代码如下:
```
// ArrayList.java
public boolean addAll(Collection<? extends E> c) {
// 转成 a 数组
Object[] a = c.toArray();
// 增加修改次数
modCount++;
// 如果 a 数组大小为 0 ,返回 ArrayList 数组无变化
int numNew = a.length;
if (numNew == 0)
return false;
// <1> 如果 elementData 剩余的空间不够,则进行扩容。要求扩容的大小,至于能够装下 a 数组。
Object[] elementData;
final int s;
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
// <2> 将 a 复制到 elementData 从 s 开始位置
System.arraycopy(a, 0, elementData, s, numNew);
// 数组大小加 numNew
size = s + numNew;
return true;
}
```
- `<1>` 处,如果 `elementData` 剩余的空间不足,则进行扩容。要求扩容的大小,至于能够装下 `a` 数组。当然,在 [「6. 数组扩容」](https://svip.iocoder.cn/JDK/Collection-ArrayList/#) 的小节,我们已经看到,如果要求扩容的空间太小,则扩容 **1.5 倍**。
- `<2>` 处,将 `a` 复制到 `elementData` 从 `s` 开始位置。
总的看下来,就是 `#add(E e)` 方法的批量版本,优势就正如我们在本节开头说的,避免可能多次扩容。
看懂这个方法后,胖友自己来看看 `#addAll(int index, Collection<? extends E> c)` 方法,从指定位置开始插入多个元素。代码如下:
```
// ArrayList.java
public boolean addAll(int index, Collection<? extends E> c) {
// 校验位置是否在数组范围内
rangeCheckForAdd(index);
// 转成 a 数组
Object[] a = c.toArray();
// 增加数组修改次数
modCount++;
// 如果 a 数组大小为 0 ,返回 ArrayList 数组无变化
int numNew = a.length;
if (numNew == 0)
return false;
// 如果 elementData 剩余的空间不够,则进行扩容。要求扩容的大小,至于能够装下 a 数组。
Object[] elementData;
final int s;
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
// 【差异点】如果 index 开始的位置已经被占用,将它们后移
int numMoved = s - index;
if (numMoved > 0)
System.arraycopy(elementData, index,
elementData, index + numNew,
numMoved);
// 将 a 复制到 elementData 从 s 开始位置
System.arraycopy(a, 0, elementData, index, numNew);
// 数组大小加 numNew
size = s + numNew;
return true;
}
```
- 重点看【差异点】部分。
# 8. 移除单个元素
`#remove(int index)` 方法,移除指定位置的元素,并返回该位置的原元素。代码如下:
```
// ArrayList.java
public E remove(int index) {
// 校验 index 不要超过 size
Objects.checkIndex(index, size);
final Object[] es = elementData;
// 记录该位置的原值
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
// <X>快速移除
fastRemove(es, index);
// 返回该位置的原值
return oldValue;
}
```
- 重点是 `<X>` 处,调用 `#fastRemove(Object[] es, int i)` 方法,快速移除。代码如下
```
// ArrayList.java
private void fastRemove(Object[] es, int i) {
// 增加数组修改次数
modCount++;
// <Y>如果 i 不是移除最末尾的元素,则将 i + 1 位置的数组往前挪
final int newSize;
if ((newSize = size - 1) > i) // -1 的原因是size 是从 1 开始,而数组下标是从 0 开始。
System.arraycopy(es, i + 1, es, i, newSize - i);
// 将新的末尾置为 null ,帮助 GC
es[size = newSize] = null;
}
```
- `<Y>` 处,看起来比较复杂,胖友按照“如果 i 不是移除最末尾的元素,则将 i + 1 位置的数组往前挪”来理解,就很好懂了。
`#remove(Object o)` 方法,移除首个为 `o` 的元素,并返回是否移除到。代码如下:
```
// ArrayList.java
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
// <Z> 寻找首个为 o 的位置
int i = 0;
found: {
if (o == null) { // o 为 null 的情况
for (; i < size; i++)
if (es[i] == null)
break found;
} else { // o 非 null 的情况
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
// 如果没找到,返回 false
return false;
}
// 快速移除
fastRemove(es, i);
// 找到了,返回 true
return true;
}
```
- 和 `#remove(int index)` 差不多,就是在 `<Z>` 处,改成获得首个为 `o` 的位置,之后就调用 `#fastRemove(Object[] es, int i)` 方法,快速移除即可。
# 9. 移除多个元素
我们先来看 `#removeRange(int fromIndex, int toIndex)` 方法,批量移除 `[fromIndex, toIndex)` 的多个元素,注意不包括 `toIndex` 的元素噢。代码如下:
```
// ArrayList.java
protected void removeRange(int fromIndex, int toIndex) {
// 范围不正确,抛出 IndexOutOfBoundsException 异常
if (fromIndex > toIndex) {
throw new IndexOutOfBoundsException(
outOfBoundsMsg(fromIndex, toIndex));
}
// 增加数组修改次数
modCount++;
// <X> 移除 [fromIndex, toIndex) 的多个元素
shiftTailOverGap(elementData, fromIndex, toIndex);
}
private static String outOfBoundsMsg(int fromIndex, int toIndex) {
return "From Index: " + fromIndex + " > To Index: " + toIndex;
}
```
- `<X>` 处,调用 `#shiftTailOverGap(Object[] es, int lo, int hi)` 方法,移除 `[fromIndex, toIndex)` 的多个元素。代码如下:
```
// ArrayList.java
private void shiftTailOverGap(Object[] es, int lo, int hi) {
// 将 es 从 hi 位置开始的元素,移到 lo 位置开始。
System.arraycopy(es, hi, es, lo, size - hi);
// 将从 [size - hi + lo, size) 的元素置空,因为已经被挪到前面了。
for (int to = size, i = (size -= hi - lo); i < to; i++)
es[i] = null;
}
```
- 和 `#fastRemove(Object[] es, int i)` 方法一样的套路,先挪后置 `null` 。
- 有一点要注意ArrayList 特别喜欢把多行代码写成一行。所以,可能会有胖又会有疑惑,貌似这里没有修改数组的大小 `size` 啊?答案在 `i = (size -= hi - lo)` ,简直到精简到难懂。
`#removeAll(Collection<?> c)` 方法,批量移除指定的多个元素。实现逻辑比较简单,但是看起来会比较绕。简单来说,通过两个变量 `w`(写入位置)和 `r`(读取位置),按照 `r` 顺序遍历数组(`elementData`),如果不存在于指定的多个元素中,则写入到 `elementData` 的 `w` 位置,然后 `w` 位置 + 1 ,跳到下一个写入位置。通过这样的方式,实现将不存在 `elementData` 覆盖写到 `w` 位置。可能理解起来有点绕,当然看代码也会有点绕绕,嘿嘿。代码如下:
```
// ArrayList.java
public boolean removeAll(Collection<?> c) {
return batchRemove(c, false, 0, size);
}
```
- 调用 `#batchRemove(Collection<?> c, boolean complement, final int from, final int end)` 方法,批量移除指定的多个元素。代码如下:
```
// ArrayList.java
boolean batchRemove(Collection<?> c, boolean complement, final int from, final int end) {
// 校验 c 非 null 。
Objects.requireNonNull(c);
final Object[] es = elementData;
int r;
// Optimize for initial run of survivors
// <1> 优化,顺序遍历 elementData 数组,找到第一个不符合 complement ,然后结束遍历。
for (r = from;; r++) {
// <1.1> 遍历到尾,都没不符合条件的,直接返回 false 。
if (r == end)
return false;
// <1.2> 如果包含结果不符合 complement 时,结束
if (c.contains(es[r]) != complement)
break;
}
// <2> 设置开始写入 w 为 r ,注意不是 r++ 。
// r++ 后,用于读取下一个位置的元素。因为通过上的优化循环,我们已经 es[r] 是不符合条件的。
int w = r++;
try {
// <3> 继续遍历 elementData 数组,如何符合条件,则进行移除
for (Object e; r < end; r++)
if (c.contains(e = es[r]) == complement) // 判断符合条件
es[w++] = e; // 移除的方式,通过将当前值 e 写入到 w 位置,然后 w 跳到下一个位置。
} catch (Throwable ex) {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
// <4> 如果 contains 方法发生异常,则将 es 从 r 位置的数据写入到 es 从 w 开始的位置
System.arraycopy(es, r, es, w, end - r);
w += end - r;
// 继续抛出异常
throw ex;
} finally { // <5>
// 增加数组修改次数
modCount += end - w;
// 将数组 [w, end) 位置赋值为 null 。
shiftTailOverGap(es, w, end);
}
return true;
}
```
- 不要慌,我们先一起看下每一小块的逻辑。然后,胖友自己调试下,妥妥的就明白了。
- ```
complement
```
参数,翻译过来是“补足”的意思。怎么理解呢?表示如果
```
elementData
```
元素在
```
c
```
集合中时,是否保留。
- 如果 `complement` 为 `false` 时,表示在集合中,就不保留,这显然符合 `#removeAll(Collection<?> c)` 方法要移除的意图。
- 如果 `complement` 为 `true` 时,表示在集合中,就暴露,这符合我们后面会看到的 `#retainAll(Collection<?> c)` 方法要求交集的意图。
- ```
<1>
```
处,首先我们要知道这是一个基于 Optimize 优化的目的。我们是希望先判断是否
```
elementData
```
没有任何一个符合
```
c
```
的,这样就无需进行执行对应的移除逻辑。但是,我们又希望能够避免重复遍历,于是就有了这样一块的逻辑。总的来说,这块逻辑的目的是,优化,顺序遍历
```
elementData
```
数组,找到第一个不符合
```
complement
```
,然后结束遍历。
- `<1.1>` 处,遍历到尾,都没不符合条件的,直接返回 `false` 。也就是说,丫根就不需要进行移除的逻辑。
- `<1.2>` 处,如果包含结果不符合 `complement` 时,结束循环。可能有点难理解,我们来举个例子。假设 `elementData` 是 `[1, 2, 3, 1]` 时,`c` 是 `[2]` 时,那么在遍历第 0 个元素 `1` 时,则 `c.contains(es[r]) != complement => false != false` 不符合,所以继续缓存;然后,在遍历第 1 个元素 `2` 时,`c.contains(es[r]) != complement => true != false` 符合,所以结束循环。此时,我们便找到了第一个需要移除的元素的位置。当然,移除不是在这里执行,我们继续往下看。😈 淡定~
- `<2>` 处,设置开始写入 `w` 为 `r` ,注意不是 `r++` 。这样,我们后续在循环 `elementData` 数组,就会从 `w` 开始写入。并且此时,`r` 也跳到了下一个位置,这样间接我们可以发现,`w` 位置的元素已经被“跳过”了。
- `<3>` 处,继续遍历 `elementData` 数组,如何符合条件,则进行移除。可能有点难理解,我们继续上述例子。遍历第 2 个元素 `3` 时候,`c.contains(es[r]) == complement => false == false` 符合,所以将 `3` 写入到 `w` 位置,同时 `w` 指向下一个位置;遍历第三个元素 `1` 时候,`c.contains(es[r]) == complement => true == false` 不符合,所以不进行任何操作。
- `<4>` 处,如果 contains 方法发生异常,则将 `es` 从 `r` 位置的数据写入到 `es` 从 `w` 开始的位置。这样,保证我们剩余未遍历到的元素,能够挪到从从 `w` 开始的位置,避免多出来一些元素。
- `<5>` 处,是不是很熟悉,将数组 `[w, end)` 位置赋值为 `null` 。
- 还是那句话,如果觉得绕,多调试,可以手绘点图,辅助理解下哈。
`#retainAll(Collection<?> c)` 方法,求 `elementData` 数组和指定多个元素的交集。简单来说,恰好和 `#removeAll(Collection<?> c)` 相反,移除不在 `c` 中的元素。代码如下:
```
// ArrayList.java
public boolean retainAll(Collection<?> c) {
return batchRemove(c, true, 0, size);
}
```
- 试着按照艿艿上面解释的,自己走一波。
# 10. 查找单个元素
`#indexOf(Object o)` 方法,查找首个为指定元素的位置。代码如下:
```
// ArrayList.java
public int indexOf(Object o) {
return indexOfRange(o, 0, size);
}
int indexOfRange(Object o, int start, int end) {
Object[] es = elementData;
// o 为 null 的情况
if (o == null) {
for (int i = start; i < end; i++) {
if (es[i] == null) {
return i;
}
}
// o 非 null 的情况
} else {
for (int i = start; i < end; i++) {
if (o.equals(es[i])) {
return i;
}
}
}
// 找不到,返回 -1
return -1;
}
```
而 `#contains(Object o)` 方法,就是基于该方法实现。代码如下:
```
// ArrayList.java
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
```
有时我们需要查找最后一个为指定元素的位置,所以会使用到 `#lastIndexOf(Object o)` 方法。代码如下:
```
// ArrayList.java
public int lastIndexOf(Object o) {
return lastIndexOfRange(o, 0, size);
}
int lastIndexOfRange(Object o, int start, int end) {
Object[] es = elementData;
// o 为 null 的情况
if (o == null) {
for (int i = end - 1; i >= start; i--) { // 倒序
if (es[i] == null) {
return i;
}
}
// o 非 null 的情况
} else {
for (int i = end - 1; i >= start; i--) { // 倒序
if (o.equals(es[i])) {
return i;
}
}
}
// 找不到,返回 -1
return -1;
}
```
# 11. 获得指定位置的元素
`#get(int index)` 方法,获得指定位置的元素。代码如下:
```
// ArrayList.java
public E get(int index) {
// 校验 index 不要超过 size
Objects.checkIndex(index, size);
// 获得 index 位置的元素
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
```
- 随机访问 `index` 位置的元素,时间复杂度为 O(1) 。
# 12. 设置指定位置的元素
`#set(int index, E element)` 方法,设置指定位置的元素。代码如下:
```
// ArrayList.java
public E set(int index, E element) {
// 校验 index 不要超过 size
Objects.checkIndex(index, size);
// 获得 index 位置的原元素
E oldValue = elementData(index);
// 修改 index 位置为新元素
elementData[index] = element;
// 返回 index 位置的原元素
return oldValue;
}
```
# 13. 转换成数组
`#toArray()` 方法,将 ArrayList 转换成 `[]` 数组。代码如下:
```
// ArrayList.java
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
// Arrays.java
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
```
- 注意,返回的是 `Object[]` 类型噢。
实际场景下,我们可能想要指定 `T` 泛型的数组,那么我们就需要使用到 `#toArray(T[] a)` 方法。代码如下:
```
// ArrayList.java
public <T> T[] toArray(T[] a) {
// <1> 如果传入的数组小于 size 大小,则直接复制一个新数组返回
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
// <2> 将 elementData 复制到 a 中
System.arraycopy(elementData, 0, a, 0, size);
// <2.1> 如果传入的数组大于 size 大小,则将 size 赋值为 null
if (a.length > size)
a[size] = null;
// <2.2> 返回 a
return a;
}
```
- 分成 2 个情况,根据传入的 `a` 数组是否足够大。
- `<1>` 处,如果传入的数组小于 `size` 大小,则直接复制一个新数组返回。一般情况下,我们不会这么干。
- ```
<2>
```
处,将
```
elementData
```
复制到
```
a
```
中。
- `<2.1>` 处,如果传入的数组大于 `size` 大小,则将 `size` 位置赋值为 `null` 。额,有点没搞懂这个有啥目的。
- `<2.2>` 处,返回传入的 `a` 。很稳。
- 考虑到 `<1>` 处,可能会返回一个新数组,所以即使 `<2>` 返回的就是 `a` 数组,最好使用还是按照 `a = list.toArray(a)` 。
# 14. 求哈希值
`#hashCode()` 方法,求 ArrayList 的哈希值。代码如下:
```
// ArrayList.java
public int hashCode() {
// 获得当前的数组修改次数
int expectedModCount = modCount;
// 计算哈希值
int hash = hashCodeRange(0, size);
// 如果修改次数发生改变,则抛出 ConcurrentModificationException 异常
checkForComodification(expectedModCount);
return hash;
}
int hashCodeRange(int from, int to) {
final Object[] es = elementData;
// 如果 to 超过大小,则抛出 ConcurrentModificationException 异常
if (to > es.length) {
throw new ConcurrentModificationException();
}
// 遍历每个元素,* 31 求哈希。
int hashCode = 1;
for (int i = from; i < to; i++) {
Object e = es[i];
hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
}
return hashCode;
}
```
- 可能胖友会好奇,为什么使用 31 作为乘子呢?可以看看 [《科普:为什么 String hashCode 方法选择数字 31 作为乘子》](http://www.iocoder.cn/Fight/Why-did-the-String-hashCode-method-select-the-number-31-as-a-multiplier/?vip&self) 。
# 15. 判断相等
`#equals(Object o)` 方法,判断是否相等。代码如下:
```
// ArrayList.java
public boolean equals(Object o) {
// 如果是自己,直接返回相等
if (o == this) {
return true;
}
// 如果不为 List 类型,直接不相等
if (!(o instanceof List)) {
return false;
}
// 获得当前的数组修改次数
final int expectedModCount = modCount;
// ArrayList can be subclassed and given arbitrary behavior, but we can
// still deal with the common case where o is ArrayList precisely
// <X> 根据不同类型,调用不同比对的方法。主要考虑 ArrayList 可以直接使用其 elementData 属性,性能更优。
boolean equal = (o.getClass() == ArrayList.class)
? equalsArrayList((ArrayList<?>) o)
: equalsRange((List<?>) o, 0, size);
// 如果修改次数发生改变,则抛出 ConcurrentModificationException 异常
checkForComodification(expectedModCount);
return equal;
}
```
- 可能第一眼让胖友比较费解的是,为什么根据类型是否为 ArrayList ,调用了两个不同的方法去比对呢?因为普通的 List ,我们只能使用 Iterator 进行迭代,相比 ArrayList 的 `elementData` 属性遍历,性能会略低一些。😈 处处是细节哈。
- 这两个方法的代码如下,已经添加详细注释。代码如下:
```
// ArrayList.java
boolean equalsRange(List<?> other, int from, int to) {
// 如果 to 大于 es 大小,说明说明发生改变,抛出 ConcurrentModificationException 异常
final Object[] es = elementData;
if (to > es.length) {
throw new ConcurrentModificationException();
}
// 通过迭代器遍历 other ,然后逐个元素对比
var oit = other.iterator();
for (; from < to; from++) {
// 如果 oit 没有下一个,或者元素不相等,返回 false 不匹配
if (!oit.hasNext() || !Objects.equals(es[from], oit.next())) {
return false;
}
}
// 通过 oit 是否遍历完。实现大小是否相等的效果。
return !oit.hasNext();
}
private boolean equalsArrayList(ArrayList<?> other) {
// 获得 other 数组修改次数
final int otherModCount = other.modCount;
final int s = size;
boolean equal;
// 判断数组大小是否相等
if (equal = (s == other.size)) {
final Object[] otherEs = other.elementData;
final Object[] es = elementData;
// 如果 s 大于 es 或者 otherEs 的长度,说明发生改变,抛出 ConcurrentModificationException 异常
if (s > es.length || s > otherEs.length) {
throw new ConcurrentModificationException();
}
// 遍历,逐个比较每个元素是否相等
for (int i = 0; i < s; i++) {
if (!Objects.equals(es[i], otherEs[i])) {
equal = false;
break; // 如果不相等,则 break
}
}
}
// 如果 other 修改次数发生改变,则抛出 ConcurrentModificationException 异常
other.checkForComodification(otherModCount);
return equal;
}
```
# 16. 清空数组
`#clear()` 方法,清空数组。代码如下:
```
// ArrayList.java
public void clear() {
// 获得当前的数组修改次数
modCount++;
// 遍历数组,倒序设置为 null
final Object[] es = elementData;
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;
}
```
# 17. 序列化数组
`#writeObject(java.io.ObjectOutputStream s)` 方法,实现 ArrayList 的序列化。代码如下:
```
// ArrayList.java
@java.io.Serial
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out element count, and any hidden stuff
// 获得当前的数组修改次数
int expectedModCount = modCount;
// <1> 写入非静态属性、非 transient 属性
s.defaultWriteObject();
// Write out size as capacity for behavioral compatibility with clone()
// <2> 写入 size ,主要为了与 clone 方法的兼容
s.writeInt(size);
// Write out all elements in the proper order.
// <3> 逐个写入 elementData 数组的元素
for (int i = 0; i < size; i++) {
s.writeObject(elementData[i]);
}
// 如果 other 修改次数发生改变,则抛出 ConcurrentModificationException 异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
```
- `<1>` 处,调用 `ObjectOutputStream#defaultWriteObject()` 方法,写入非静态属性、非 `transient` 属性。可能有些胖友不了解 Java 的序列化相关的知识,可以看看 [《Serializable 原理》](https://juejin.im/entry/5bf622436fb9a04a0b21cbe7) 文章。
- `<2>` 处,写入 `size` ,主要为了与 clone 方法的兼容。不过艿艿也觉得挺奇怪的,明明在 `<1>` 处,已经写入了 `size` ,这里怎么还来这么一出呢?各种翻查资料,暂时只看到 [《源码分析ArrayList 的 writeobject 方法中的实现是否多此一举?》](https://www.zhihu.com/question/41512382) 有个讨论。
- `<3>` 吹,逐个写入 `elementData` 元素的数组。我们回过来看下 `elementData` 的定义,它是一个 `transient` 修饰的属性。为什么呢?因为 `elementData` 数组,并不一定是全满的,而可能是扩容的时候有一定的预留,如果直接序列化,会有很多空间的浪费,所以只序列化从 `[0, size)` 的元素,减少空间的占用。
# 18. 反序列化数组
`#readObject(java.io.ObjectInputStream s)` 方法,反序列化数组。代码如下:
```
// ArrayList.java
@java.io.Serial
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
// 读取非静态属性、非 transient 属性
s.defaultReadObject();
// Read in capacity
// 读取 size ,不过忽略不用
s.readInt(); // ignored
if (size > 0) {
// like clone(), allocate array based upon size not capacity
SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size); // 不知道作甚,哈哈哈。
// 创建 elements 数组
Object[] elements = new Object[size];
// Read in all elements in the proper order.
// 逐个读取
for (int i = 0; i < size; i++) {
elements[i] = s.readObject();
}
// 赋值给 elementData
elementData = elements;
} else if (size == 0) {
// 如果 size 是 0 ,则直接使用空数组
elementData = EMPTY_ELEMENTDATA;
} else {
throw new java.io.InvalidObjectException("Invalid size: " + size);
}
}
```
- 和序列化的过程,恰好相反(哈哈哈,不然还想咋样),一眼就看的明白。
# 19. 克隆
`#clone()` 方法,克隆 ArrayList 对象。代码如下:
```
// ArrayList.java
public Object clone() {
try {
// 调用父类,进行克隆
ArrayList<?> v = (ArrayList<?>) super.clone();
// 拷贝一个新的数组
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);
}
}
```
- 注意,`elementData` 是重新拷贝出来的新的数组,避免和原数组共享。
# 20. 创建子数组
`#subList(int fromIndex, int toIndex)` 方法,创建 ArrayList 的子数组。代码如下:
```
// ArrayList.java
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList<>(this, fromIndex, toIndex);
}
private static class SubList<E> extends AbstractList<E> implements RandomAccess {
/**
* 根 ArrayList
*/
private final ArrayList<E> root;
/**
* 父 SubList
*/
private final SubList<E> parent;
/**
* 起始位置
*/
private final int offset;
/**
* 大小
*/
private int size;
// ... 省略代码
}
```
- 实际使用时一定要注意SubList 不是一个**只读**数组,而是和根数组 `root` 共享相同的 `elementData` 数组,只是说限制了 `[fromIndex, toIndex)` 的范围。
- 这块的源码,并不复杂,所以这里也就不展开了。一般情况下,我们也不需要了解它的源码,嘿嘿。
# 21. 创建 Iterator 迭代器
`#iterator()` 方法,创建迭代器。一般情况下,我们使用迭代器遍历 ArrayList、LinkedList 等等 List 的实现类。代码如下:
```
// ArrayList.java
public Iterator<E> iterator() {
return new Itr();
}
```
- 创建 Itr 迭代器。Itr 实现 [`java.util.Iterator`](https://github.com/YunaiV/openjdk/blob/master/src/java.base/share/classes/java/util/List.java) 接口,是 ArrayList 的内部类。虽然说 AbstractList 也提供了一个 Itr 的实现,但是 ArrayList 为了更好的性能所以自己实现了在其类上也有注释“An optimized version of AbstractList.Itr”。
Itr 一共有 3 个属性,如下:
```
// ArrayList.java#Itr
/**
* 下一个访问元素的位置,从下标 0 开始。
*/
int cursor; // index of next element to return
/**
* 上一次访问元素的位置。
*
* 1. 初始化为 -1 ,表示无上一个访问的元素
* 2. 遍历到下一个元素时lastRet 会指向当前元素,而 cursor 会指向下一个元素。这样,如果我们要实现 remove 方法,移除当前元素,就可以实现了。
* 3. 移除元素时,设置为 -1 ,表示最后访问的元素不存在了,都被移除咧。
*/
int lastRet = -1; // index of last element returned; -1 if no such
/**
* 创建迭代器时,数组修改次数。
*
* 在迭代过程中,如果数组发生了变化,会抛出 ConcurrentModificationException 异常。
*/
int expectedModCount = modCount;
// prevent creating a synthetic constructor
Itr() {}
```
- 每个属性,胖友自己看看注释噢。
下面,让我们来看看 Itr 对 Iterator 的 4 个实现方法。
`#hasNext()` 方法,判断是否还可以继续迭代。代码如下:
```
// ArrayList.java#Itr
public boolean hasNext() {
return cursor != size;
}
```
- `cursor` 如果等于 `size` ,说明已经到数组末尾,无法继续迭代了。
`#next()` 方法,下一个元素。代码如下:
```
// ArrayList.java#Itr
public E next() {
// 校验是否数组发生了变化
checkForComodification();
// 判断如果超过 size 范围,抛出 NoSuchElementException 异常
int i = cursor; // <1> i 记录当前 cursor 的位置
if (i >= size)
throw new NoSuchElementException();
// 判断如果超过 elementData 大小,说明可能被修改了,抛出 ConcurrentModificationException 异常
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
// <2> cursor 指向下一个位置
cursor = i + 1;
// <3> 返回当前位置的元素
return (E) elementData[lastRet = i]; // <4> 此处,会将 lastRet 指向当前位置
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
```
- `<1>` 处,记录当前 `cursor` 的位置。因为我们当前返回的就是要求 `cursor` 位置的元素。
- `<2>` 处,`cursor` 指向下一个位置。
- `<3>` 处,返回当前位置的元素。同时在 `<4>` 处,会将 `lastRet` 指向当前位置。
`#remove()` 方法,移除当前元素。代码如下:
```
// ArrayList.java#Itr
public void remove() {
// 如果 lastRet 小于 0 ,说明没有指向任何元素,抛出 IllegalStateException 异常
if (lastRet < 0)
throw new IllegalStateException();
// 校验是否数组发生了变化
checkForComodification();
try {
// <1> 移除 lastRet 位置的元素
ArrayList.this.remove(lastRet);
// <2> cursor 指向 lastRet 位置,因为被移了,所以需要后退下
cursor = lastRet;
// <3> lastRet 标记为 -1 ,因为当前元素被移除了
lastRet = -1;
// <4> 记录新的数组的修改次数
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
```
- `<1>` 处,调用 `#remove(int index)` 方法,移除 `lastRet` 位置的元素。所以,如果要注意,如果移除元素比较前面,会将后面位置的往前挪,即复制,可能比较消耗性能。
- `<2>` 处,`cursor` 指向 `lastRet` 位置,因为被移了,所以需要后退下。
- `<3>` 处,`lastRet` 标记为 `-1` ,因为当前元素被移除了。
- `<4>` 处,记录新的数组的修改次数。因为此处修改了数组,如果不修改下,后续迭代肯定会报错。
`#forEachRemaining(Consumer<? super E> action)` 方法,消费剩余未迭代的元素。代码如下:
```
// ArrayList.java#Itr
@Override
public void forEachRemaining(Consumer<? super E> action) {
// 要求 action 非空
Objects.requireNonNull(action);
// 获得当前数组大小
final int size = ArrayList.this.size;
// 记录 i 指向 cursor
int i = cursor;
if (i < size) {
// 判断如果超过 elementData 大小,说明可能被修改了,抛出 ConcurrentModificationException 异常
final Object[] es = elementData;
if (i >= es.length)
throw new ConcurrentModificationException();
// 逐个处理
for (; i < size && modCount == expectedModCount; i++)
action.accept(elementAt(es, i));
// update once at end to reduce heap write traffic
// 更新 cursor 和 lastRet 的指向
cursor = i;
lastRet = i - 1;
// 校验是否数组发生了变化
checkForComodification();
}
}
```
- 比较简单,胖友自己瞅瞅。貌似平时这个方法用的不是很多。
# 22. 创建 ListIterator 迭代器
> 艿艿:可能一些胖友不了解 ListIterator 迭代器,因为平时使用不多。可以先去看看 [《Java 集合框架之 Iterator 和 ListIterator》](https://my.oschina.net/jackieyeah/blog/220134) 。简单来说ListIterator 是为 List 设计的,功能更强大的 Iterator 迭代器。
`#listIterator(...)` 方法,创建 ListIterator 迭代器。代码如下:
```
// ArrayList.java
public ListIterator<E> listIterator(int index) {
rangeCheckForAdd(index);
return new ListItr(index);
}
public ListIterator<E> listIterator() {
return new ListItr(0);
}
```
- 创建 ListItr 迭代器。ListItr 实现 [`java.util.ListIterator`](https://github.com/YunaiV/openjdk/blob/master/src/java.base/share/classes/java/util/ListIterator.java) 接口,是 ArrayList 的内部类。虽然说 AbstractList 也提供了一个 ListItr 的实现,但是 ArrayList 为了更好的性能所以自己实现了在其类上也有注释“An optimized version of AbstractList.ListItr”。
ListItr 直接继承 Itr 类,无自定义的属性。代码如下:
```
// ArrayList.java#ListItr
ListItr(int index) {
super();
cursor = index;
}
```
- 可以手动设置指定的位置开始迭代。
因为 ListItr 的实现代码比较简单,我们就不逐个来看了,直接贴加了注释的代码。代码如下:
```
// ArrayList.java#ListItr
/**
* @return 是否有前一个
*/
public boolean hasPrevious() {
return cursor != 0;
}
/**
* @return 下一个位置
*/
public int nextIndex() {
return cursor;
}
/**
* @return 前一个位置
*/
public int previousIndex() {
return cursor - 1;
}
/**
* @return 前一个元素
*/
@SuppressWarnings("unchecked")
public E previous() {
// 校验是否数组发生了变化
checkForComodification();
// 判断如果小于 0 ,抛出 NoSuchElementException 异常
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
// 判断如果超过 elementData 大小,说明可能被修改了,抛出 ConcurrentModificationException 异常
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
// cursor 指向上一个位置
cursor = i;
// 返回当前位置的元素
return (E) elementData[lastRet = i]; // 此处,会将 lastRet 指向当前位置
}
/**
* 设置当前元素
*
* @param e 设置的元素
*/
public void set(E e) {
// 如果 lastRet 无指向,抛出 IllegalStateException 异常
if (lastRet < 0)
throw new IllegalStateException();
// 校验是否数组发生了变化
checkForComodification();
try {
// 设置
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
/**
* 添加元素当当前位置
*
* @param e 添加的元素
*/
public void add(E e) {
// 校验是否数组发生了变化
checkForComodification();
try {
// 添加元素到当前位置
int i = cursor;
ArrayList.this.add(i, e);
// cursor 指向下一个位置,因为当前位置添加了新的元素,所以需要后挪
cursor = i + 1;
// lastRet 标记为 -1 ,因为当前元素并未访问
lastRet = -1;
// 记录新的数组的修改次数
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
```
# 666. 彩蛋
咳咳咳,比想象中的长的多的一篇文章。并且实际上,我们还有几个 ArrayList 的方法的解析没有写,如下:
- `#spliterator()`
- `#removeIf(Predicate<? super E> filter)`
- `#replaceAll(UnaryOperator<E> operator)`
- `#sort(Comparator<? super E> c)`
- `#forEach(Consumer<? super E> action)`
哈哈,也是比较简单的方法,胖友自己可以解决一波的哈。就当,课后作业?!嘿嘿。
下面,我们来对 ArrayList 做一个简单的小结:
- ArrayList 是基于 `[]` 数组实现的 List 实现类,支持在数组容量不够时,一般按照 **1.5** 倍**自动**扩容。同时,它支持**手动**扩容、**手动**缩容。
- ArrayList 随机访问时间复杂度是 O(1) ,查找指定元素的**平均**时间复杂度是 O(n) 。
> 可能胖友对时间复杂度的计算方式不是很了解,可以看看 [《算法复杂度分析(上):分析算法运行时,时间资源及空间资源的消耗》](https://www.cnblogs.com/jonins/p/9950799.html) 和 [《算法复杂度分析(下):最好、最坏、平均、均摊等时间复杂度概述》](https://www.cnblogs.com/jonins/p/9956752.html) 两文。
- ArrayList 移除指定位置的元素的最好时间复杂度是 O(1) ,最坏时间复杂度是 O(n) ,平均时间复杂度是 O(n) 。
> 最好时间复杂度发生在末尾移除的情况。
- ArrayList 移除指定元素的时间复杂度是 O(n) 。
> 因为首先需要进行查询,然后在使用移除指定位置的元素,无论怎么计算,都需要 O(n) 的时间复杂度。
- ArrayList 添加元素的最好时间复杂度是 O(1) ,最坏时间复杂度是 O(n) ,平均时间复杂度是 O(n) 。
> 最好时间复杂度发生在末尾添加的情况。
结尾在抛个拓展,在 Redis String 的数据结构,实现方式是类似 Java ArrayList 的方式,感兴趣的胖友可以自己去瞅瞅。