集合框架--LinkedList

67 阅读16分钟

1. 概述

LinkedList 采用双向链表结构存储元素,在对 LinkedList 进行插入和删除操作时,只需在对应的节点上插入或删除元素,并将上一个节点元素的下一个节点的指针指向该节点即可,需要从链表头部一直遍历到该节点为止,因此随机访问速度很慢。除此之外,LinkedList 还提供了在 List 接口中未定义的方法,用于操作链表头部和尾部的元素,因此有时可以被当作堆栈、队列或者双向队列使用。

LinkedList 集合底层实现的数据结构是双向链表,集合中的元素可以为 null,允许存入重复的数据。

LinkedList 是非线程安全的。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{}
  1. LinkedList 继承 AbstractSequentialList 类,实现了 List 接口和 Deque 双向队列接口,因此 LinkedList 不仅拥有 List 的相关操作方法也有队列的相关操作方法。
  2. 实现了 Serializable 接口和 Cloneable 接口,具有序列化和克隆的特性

什么是双向链表?

链表与数组的数据结构不同,双向链表是链表的一种子数据结构。

特点:每个节点上都有3个字段:当前节点的数据字段(data),指向上一个节点的字段(prev),指向下一个节点的字段(next)

2. 成员变量内容

LinkedList 中双向链表的实现:

LinkedList 是节点实现完全符合双向链表的数据结构要求。

private static class Node<E> {
    // 当前节点的元素值
    E item;
    // 下一个节点的索引
    Node<E> next;
    // 上一个节点的索引
    Node<E> prev;
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

LinkedList 主要成员变量有一下三个:

transient int size = 0;  // LinkedList 中的节点的个数
/**
 * Pointer to first node.
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;  // 链表中的第一个节点
/**
 * Pointer to last node.
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;  // 链表中的最后一个节点

在成员变量中保存了链表的 first 节点和 last 节点,为什么呢?链表的数据结构的相对于数组来说优点是增删,缺点是查找。如果我们保存了 LinkedList 的头尾两端,当我们需要索引来查找节点的时候,我们可以根据 index 和 size/2 的大小来决定从头查找还是从尾部查找。

3. 构造函数

/**
 * 生成一个空的链表,first = last = null
 */
public LinkedList() {
}
/**
 * 传入一个集合类来构造一个具有一定元素的 LinkedList 集合
 *
 * @param  c 内部元素将按照顺序作为 LinkedList 的节点
 * @throws NullPointerException 参数集合为空抛出异常
 */
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

有参构造函数调用的是 addAll(c) 方法,实际上调用的是 addAll(size,c) 方法,在外部单独调用时,将指定集合的元素作为节点,添加到 LinkedList 链表尾部;而 addAll(size,c) 可以将集合元素插入到指定索引节点。

/**
按照指定集合的迭代器返回的顺序,将指定集合中的所有元素追加到此列表的末尾。 如果在操作进行时修改了指定的集合,则此操作的行为是未定义的。 (请注意,如果指定的集合是此列表并且它是非空的,则会发生这种情况。)
*/
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}
/**
 * 在 index 节点前插入包含所有 c 集合元素的节点
 * 返回值表示是否成功添加了对应的元素
*/
public boolean addAll(int index, Collection<? extends E> c) {
    // 查看索引是否满足 0<= index <=size 的要求
    checkPositionIndex(index);
    // 将 集合转换为数组
    Object[] a = c.toArray();
    // 数组的长度
    int numNew = a.length;
    // 检查数组的长度,如果为 0 则直接返回 false 表示没有添加任何的元素
    if (numNew == 0)
        return false;
    // 保存 index 当前的节点为 succ,当前节点的上一个节点为 pred
    Node<E> pred, succ;
    // 如果 index==size 表示在链表尾部插入
    if (index == size) {
        succ = null;
        pred = last;
    } else {
        succ = node(index);
        pred = succ.prev;
    }
    // 遍历数组将对应的元素包装成节点添加到链表中
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);
        // 如果 pred 为空表示 LinkedList 集合中还没有元素
        // 生成的第一个节点将作为头节点赋值给first成员变量
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        pred = newNode;
    }
    // 如果 index 位置的元素为 null 则遍历数组后 pred 所指向的节点即为新链表的末节点,赋值给 last 成员变量
    if (succ == null) {
        last = pred;
    } else {
        // 否则将 pred 的 next 索引指向 succ,succ 的 prev 索引指向 pred
        pred.next = succ;
        succ.prev = pred;
    }
    // 更新当前链表的长度 size 并返回 true 表示添加成功
    size += numNew;
    modCount++;
    return true;
}
​
private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
 * Tells if the argument is the index of a valid position for an
 * iterator or an add operation.
    判断参数是否是迭代器或添加操作的有效位置的索引。
 */
private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}

批量添加节点步骤:

  1. 检查索引节点是否合法,不合法抛出异常
  2. 保存 index 位置的节点和index-1位置的节点
  3. 将参数集合转换为数组,循环将数组中的元素封装成节点添加到链表中
  4. 更新链表长度并返回 true 表示添加成功

4. 添加节点方法

LinkedList 是使用链表的数据结构实现的,不同于数组可以方便的在头尾插入一个节点元素,LinkedList 的 add 方法默认是在链表尾部添加节点。

/**
 * Appends the specified element to the end of this list.
 *
 * <p>This method is equivalent to {@link #addLast}.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    linkLast(e);
    return true;
}
​
/**
 * Inserts the specified element at the beginning of this list.
 *
 * @param e the element to add
 */
public void addFirst(E e) {
    linkFirst(e);
}
​
/**
 * Appends the specified element to the end of this list.
 *
 * <p>This method is equivalent to {@link #add}.
 *
 * @param e the element to add
 */
public void addLast(E e) {
    linkLast(e);
}

如上是三个添加方法,我们可以看到他们内部都调用了 linkXXX 方法

/**
 * Links e as first element. 添加一个元素在链表的头节点位置
 */
private void linkFirst(E e) {
    // 之前的头节点
    final Node<E> f = first;
    // 为要添加的元素包装成 Node 节点
    // 因为要作为头节点来插入,所以它的头节点为 null
    // 它的 next 将指向之前的头节点
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    // 添加之前链表为空则该节点为头节点也为尾节点
    if (f == null)
        last = newNode;
    else
        // 否则之前头节点的 prev 指向新添加节点
        f.prev = newNode;
    size++;
    modCount++;
}
/**
 * Links e as last element. 在链表末尾添加一个节点
 */
void linkLast(E e) {
    // 之前的节点
    final Node<E> l = last;
    // 构建新的节点,prev 指向之前的尾节点
    final Node<E> newNode = new Node<>(l, e, null);
    // last 索引指向新节点
    last = newNode;
    if (l == null)
        // 若之前链表为空则新节点也作为头节点
        first = newNode;
    else
        // 否则将之前的尾节点指向新的节点
        l.next = newNode;
    size++;
    modCount++;
}

以上为几种常用的添加元素的方法,LinkedList 还提供了一种在指定 index 位置插入节点的方法

/**
 * Inserts the specified element at the specified position in this list.
 * Shifts the element currently at that position (if any) and any
 * subsequent elements to the right (adds one to their indices).
 *	在此列表中的指定位置插入指定元素。 将当前位于该位置的元素(如果有)和任何后续元素向右移动(将其索引加一)。
 * @param index index at which the specified element is to be inserted 指定元素要插入的索引
 * @param element element to be inserted 要插入的元素
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
    // 检查下标是否合法
    checkPositionIndex(index);
    // 如果 index == size 表示在尾部插入节点
    if (index == size)
        linkLast(element);
    else
        // 当 index >= 0 && index < size 的时候调用
        linkBefore(element, node(index));
}
/**
 * Returns the (non-null) Node at the specified element index.
 	返回指定元素索引处的(非空)节点
 	根据指定 index 以 size/2 为界限搜索 index 位置上的 Node
 */
Node<E> node(int index) {
    // assert isElementIndex(index);
    // 以 size / 2 界限
    // 如果 index < size/2 则从 0 开始寻找指定下标的节点
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        // 如果 index >= size/2 则从 size - 1 开始寻找指定下标的节点
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

linkeBefore 方法,在链表中间插入节点其实就是在 index 原来的节点前添加一个节点,添加节点我们需要知道的是前一个节点和当前节点。

  1. 新节点 prev 指向前一个节点
  2. 新节点前指针 next 指针指向 index 位置的节点
  3. index 位置节点 prev 指针指向新节点
  4. index 位置前节点的 next 指针指向新节点
/**
 * Inserts element e before non-null Node succ.
 	在非空节点 succ 之前插入元素 e
 */
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    // 获得要被替换节点的前指针 prev
    final Node<E> pred = succ.prev;
    // 对新添加元素构建 Node 节点
    // 新节点 prev 节点为 pred,next 节点为 succ
    final Node<E> newNode = new Node<>(pred, e, succ);
    // succ 前节点指向新节点
    succ.prev = newNode;
    // 如果前节点为空,即头节点插入了一个节点,则将新的节点赋值给 first 索引
    if (pred == null)
        first = newNode;
    else
        // 否则 pred.next 指向新节点
        pred.next = newNode;
    size++;
    modCount++;
}

5. 删除节点

/**
 * Retrieves and removes the head (first element) of this list.
 * 检索并删除此列表的头部(第一个元素)。
 * @return the head of this list 删除节点的值(element)
 * @throws NoSuchElementException if this list is empty
 	// 如果链表为空则抛出异常:NoSuchElementException
 * @since 1.5
 */
public E remove() {
    return removeFirst();
}

/**
 * Removes and returns the first element from this list.
 * 从此列表中移除并返回第一个元素。 
 * @return the first element from this list 删除节点的值(element)
 * @throws NoSuchElementException if this list is empty 如果链表为空则抛出异常:NoSuchElementException
 */
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

/**
 * Removes and returns the last element from this list.
 *	从此列表中移除并返回末尾的元素
 * @return the last element from this list 删除节点的值(element)
 * @throws NoSuchElementException if this list is empty 如果链表为空则抛出异常:NoSuchElementException
 */
public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}
/**
 * Unlinks non-null first node f.
    移除头节点
    参数 f 为待移除节点
 */
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    // 得到节点的值,用于最后返回使用
    final E element = f.item;
    // 头节点下一个节点
    final Node<E> next = f.next;
    // 释放头节点的 next 指针和 element 值,等待垃圾回收器清理
    f.item = null;
    f.next = null; // help GC
    // 将 first 索引指向新的节点
    first = next;
    // 如果 next 节点为空,即链表只有一个节点时候,last 指向 null
    if (next == null)
        last = null;
    else
        // 否则,next 的 prev 指向 null
        next.prev = null;
    size--; // 该表链表长度
    modCount++;
    // 返回删除节点的值
    return element; 
}
/**
 * Unlinks non-null last node l.
 	移除尾节点
 	参数 f 为待移除节点
 */
private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    // 尾节点的值,用于 return 使用
    final E element = l.item;
    // 尾节点前一个元素
    final Node<E> prev = l.prev;
    // 释放尾节点 prev 指向和元素值,等待 GC
    l.item = null;
    l.prev = null; // help GC
    // 将 last 指向新的尾节点
    last = prev;
    // 链表只有一个节点,first 指向 null
    if (prev == null)
        first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
}

除了上面的删除头节点 remove() 和 removeFirst() ,删除尾节点 removeLast 之外,LinkedList 还可以根据索引位置删除节点和根据节点值来删除节点。

根据索引下标删除节点:

/**
 * Removes the element at the specified position in this list.  Shifts any
 * subsequent elements to the left (subtracts one from their indices).
 * Returns the element that was removed from the list.
 *	移除列表中指定位置的元素。 将任何后续元素向左移动(从其索引中减去一个)。 返回从列表中删除的元素。
 	即删除指定索引下标位置的节点
 * @param index the index of the element to be removed 要删除的元素的索引
 * @return the element previously at the specified position 之前在指定位置的元素
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E remove(int index) {
    // 检查下标是否合法
    checkElementIndex(index);
    // 这里的 node() 方法与上面 addAll 方法调用的是相同的方法,目的就是根据索引值得到节点内容
    return unlink(node(index));
}

根据值删除节点:

/**
 * Removes the first occurrence of the specified element from this list,
 * if it is present.  If this list does not contain the element, it is
 * unchanged.  More formally, removes the element with the lowest index
 * {@code i} such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
 * (if such an element exists).  Returns {@code true} if this list
 * contained the specified element (or equivalently, if this list
 * changed as a result of the call).
 *	从此列表中移除第一次出现的指定元素(如果存在)。 如果此列表不包含该元素,则它保持不变。
 * @param o element to be removed from this list, if present 待删除元素值
 * @return {@code true} if this list contained the specified element
 */
public boolean remove(Object o) {
    // 对 null 值特殊处理
    if (o == null) {
        // 遍历链表判断
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                // 如果节点 item 值为 null 调用 unlink 方法
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

上面的两个 remove 方法中调用了 unlink 方法,如下:

/**
 * Unlinks non-null node x.
 */
E unlink(Node<E> x) {
    // assert x != null;
    // 待删除节点的值
    final E element = x.item;
    // 待删除节点的前后两个节点
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
    // 如果为头节点,则待删除节点的 next 节点则变为 frist节点
    if (prev == null) {
        first = next;
    } else {
        // 否则将前一个节点的 next 指向下一个节点
        prev.next = next;
        // 释放待删除节点的 prev 指针
        x.prev = null;
    }
    // 如果节点是尾节点,则让 last 索引指向上个节点
    if (next == null) {
        last = prev;
    } else {
        // 否则下一个节点 prev 指针指向上个节点
        next.prev = prev;
        x.next = null;
    }
    x.item = null;
    size--;
    modCount++;
    return element;
}

clear() 方法,用于删除链表所有的节点

/**
 * Removes all of the elements from this list.
 * The list will be empty after this call returns.
	从此列表中删除所有元素。此调用返回后列表将为空。
 */
public void clear() {
    // Clearing all of the links between nodes is "unnecessary", but:
    // - helps a generational GC if the discarded nodes inhabit
    //   more than one generation
    // - is sure to free memory even if there is a reachable Iterator
    // 依次清楚节点,帮助释放内存空间
    for (Node<E> x = first; x != null; ) {
        Node<E> next = x.next;
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    first = last = null;
    size = 0;
    modCount++;
}

6. 查询节点方法

LinkedList 查询节点的方法可以分为根据索引查询、获取头节点、获取尾节点三种。

/**
 * Returns the element at the specified position in this list.
 *	返回此列表中指定位置的元素
 * @param index index of the element to return
 * @return the element at the specified position in this list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
// 根据索引查询
public E get(int index) {
    checkElementIndex(index);
    // node() 方法与 在上面 addAll() 方法中介绍过,根据index获得节点信息
    return node(index).item;
}

/**
 * Returns the first element in this list.
 *	返回 first 索引指向的节点的内容
 * @return the first element in this list
 * @throws NoSuchElementException if this list is empty
 */
public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

/**
 * Returns the last element in this list.
 *	返回 last 索引指向的节点的内容
 * @return the last element in this list
 * @throws NoSuchElementException if this list is empty
 */
public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

上面是 LinkedList 根据索引方式查询节点的方法,LinkedList 还提供了一系列的判断元素在链表中位置的方法

/**
 * 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 {@code i} such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
 * or -1 if there is no such index.
 *	返回此列表中指定元素第一次出现的索引,如果此列表不包含该元素,则返回 -1。
 	如果有重复元素,返回值为从头节点起的第一个相同元素节点的索引
 * @param o element to search for 要搜索的元素
 * @return the index of the first occurrence of the specified element in
 *         this list, or -1 if this list does not contain the element
 */
public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

/**
 * Returns the index of the last occurrence of the specified element
 * in this list, or -1 if this list does not contain the element.
 * More formally, returns the highest index {@code i} such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
 * or -1 if there is no such index.
 * 返回此列表中指定元素最后一次出现的索引,如果此列表不包含该元素,则返回 -1。
 	如果有重复元素,那么返回值为从尾节点起的第一个相同的元素节点的索引
 * @param o element to search for
 * @return the index of the last occurrence of the specified element in
 *         this list, or -1 if this list does not contain the element
 */
public int lastIndexOf(Object o) {
    int index = size;
    if (o == null) {
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (x.item == null)
                return index;
        }
    } else {
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (o.equals(x.item))
                return index;
        }
    }
    return -1;
}

上面两个方法分别返回的是从头节点起第一个与参数元素匹配的节点索引和从尾节点起第一个与参数元素匹配的节点索引。

判断链表中是否有某一个元素存在方法:contains(Object o)

/**
 * Returns {@code true} if this list contains the specified element.
 * More formally, returns {@code true} if and only if this list contains
 * at least one element {@code e} such that
 * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
 *
 * @param o element whose presence in this list is to be tested
 * @return {@code true} if this list contains the specified element
 */
public boolean contains(Object o) {
    return indexOf(o) != -1;
}

7. 修改节点方法

/**
 * Replaces the element at the specified position in this list with the
 * specified element.
 *	用指定元素替换此列表中指定位置的元素。
 * @param index index of the element to replace
 * @param element element to be stored at the specified position
 * @return the element previously at the specified position
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E set(int index, E element) {
    // 检查下标是否合法
    checkElementIndex(index);
    // 前面提到 node 方法,使用 node 方法查找对应索引的节点
    Node<E> x = node(index);
    // 保存节点原有的内容值
    E oldVal = x.item;
    // 设置新的值
    x.item = element;
    // 返回旧的值
    return oldVal;
}

8. 遍历

LinkedList 调用它的 iterator 和 listIterator 的时候最终都是返回一个 ListItr 的一个对象。

private class ListItr implements ListIterator<E> {
    // 上一个遍历的节点
    private Node<E> lastReturned;
    // 下一个遍历的节点
    private Node<E> next;
    // cursor 指针下一次遍历返回的节点
    private int nextIndex;
    // 期望的操作数
    private int expectedModCount = modCount;
    
    // 根据参数 index 确定生成的迭代器 cursor 的位置
    ListItr(int index) {
        // assert isPositionIndex(index);
        // 如果 index == size 则 next 为 null 否则寻找 index 位置的节点
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }
    // 判断指针是否可以移动
    public boolean hasNext() {
        return nextIndex < size;
    }
    // 返回下一个待遍历的元素
    public E next() {
        // 检查操作数是否合法
        checkForComodification();
        // 如果 hasNext 返回 false 抛出异常,所以在调用 next 的时候应先调用 hasNext 检查
        if (!hasNext())
            throw new NoSuchElementException();
        // 移动 lastReturned 指针
        lastReturned = next;
        // 移动 next 指针
        next = next.next;
        // 移动 nextIndex cursor
        nextIndex++;
        // 返回移动后 lastReturned
        return lastReturned.item;
    }
    // 判断当前游标位置是否还有前一个元素
    public boolean hasPrevious() {
        return nextIndex > 0;
    }
    // 当前游标位置的前一个元素
    public E previous() {
        checkForComodification();
        if (!hasPrevious())
            throw new NoSuchElementException();
        // 等同于 lastReturned = next; next = (next == null) ? last : next.prev;
        // 发生在 index = size 时
        lastReturned = next = (next == null) ? last : next.prev;
        nextIndex--;
        return lastReturned.item;
    }
    public int nextIndex() {
        return nextIndex;
    }
    public int previousIndex() {
        return nextIndex - 1;
    }
    // 删除链表当前节点也就是调用 next/previous 返回的这节点,也就 lastReturned
    public void remove() {
        checkForComodification();
        if (lastReturned == null)
            throw new IllegalStateException();
        Node<E> lastNext = lastReturned.next;
        // 调用 LinkedList 的删除节点的方法
        unlink(lastReturned);
        if (next == lastReturned)
            next = lastNext;
        else
            nextIndex--;
        // 上一次所操作的节点置为空
        lastReturned = null;
        expectedModCount++;
    }
    // 设置当前遍历的节点的值
    public void set(E e) {
        if (lastReturned == null)
            throw new IllegalStateException();
        checkForComodification();
        lastReturned.item = e;
    }
    // 在next节点位置插入节点
    public void add(E e) {
        checkForComodification();
        lastReturned = null;
        if (next == null)
            linkLast(e);
        else
            linkBefore(e, next);
        nextIndex++;
        expectedModCount++;
    }
    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (modCount == expectedModCount && nextIndex < size) {
            action.accept(next.item);
            lastReturned = next;
            next = next.next;
            nextIndex++;
        }
        checkForComodification();
    }
    // 操作数是否合法
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

参考文献:

juejin.cn/post/684490…