LinkedList详解(含数据结构动画演示)

目录

  • LinkedList详解
    • 1、LinkedList的继承体系
    • 2、LinkedList的构造函数
    • 3、LinkedList的`add(E e)`方法
    • 4、LinkedList的Node节点
    • 5、双向链表的概念和Node节点的详细解释
    • 6、LinkedList的`add(E e)`方法梳理
    • 7、LinkedList的`getXXX`方法
    • 8、LinkedList的`removeXXX`方法
      • ①、removeFirst()方法 (移除LinkedList的第一个元素):
      • ②、removeLast()方法 (移除LinkedList的最后一个元素):
      • ③、remove(E e)方法 (这个方法值得深入)
      • ④、`remove(int index)`方法
    • 9、LinkedList的反向迭代器 `DescendingIterator`

LinkedList详解

LinkedList用的不多,但是其内部的双向链表实现还是值得去探讨学习的。

1、LinkedList的继承体系

public class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deque<E>, Cloneable, java.io.Serializable

在这里插入图片描述
可以看到和ArrayList还是有不少区别的

需要注意的两个点:

  • ①、LinkedList没有实现RandomAccess接口,意味着LinkedList不支持快速随机访问。这是由LinkedList的数据结构决定的。由于LinkedList 底层数据结构是链表,内存地址是不连续的,只能通过指针来定位,不像ArrayList底层是数组结构内存地址是连续的,可以通过数组的下标快速定位元素。所以LinkedList没有实现RandomAccess接口。

  • ②、LinkedList实现了Deque接口,说明LinkedList支持双向链表的操作,比如addFirst,addLast等方法。

2、LinkedList的构造函数

①、无参构造

public LinkedList() {}

②、有参构造

public LinkedList(Collection<? extends E> c) {// 调用无参构造函数,初始化LinkedList实例this();// 将传入的集合c中的所有元素添加到当前LinkedList实例中addAll(c);}

3、LinkedList的add(E e)方法

add(E e)方法

public boolean add(E e) {// 调用linkLast方法将元素e添加到链表的末尾linkLast(e);// 返回true,表示元素添加成功return true;}

linkLast(E e)方法

// 用于跟踪修改次数的计数器,主要用于迭代器的快速失败(fail-fast)机制
protected transient int modCount = 0;// 链表中元素的数量
transient int size = 0;// 链表的第一个节点引用
transient Node<E> first;// 链表的最后一个节点引用
transient Node<E> last;void linkLast(E e) {// 将当前链表的最后一个节点引用保存到局部变量l中// 这样可以方便后续对链表末尾进行操作final Node<E> l = last;// 创建一个新的节点,节点的前驱是l(即当前的最后一个节点),// 节点包含的元素是e,后继节点为null(因为新节点将成为最后一个节点)final Node<E> newNode = new Node<>(l, e, null);// 更新链表的最后一个节点引用为新创建的节点last = newNode;// 检查链表是否为空(即当前没有任何节点)// 如果链表为空(即last为null),那么新节点也是第一个节点if (l == null)// 将链表的第一个节点引用也更新为新创建的节点first = newNode;else// 如果链表不为空(即已有节点),// 那么将当前最后一个节点的next指向新创建的节点l.next = newNode;// 链表的元素数量增加1size++;// 修改计数器增加1modCount++;
}

上面的modCount在ArrayList详解的文章中已经介绍过来,这里就不再赘述。
我们需要关注LinkedList的核心 Node,可以叫节点。

4、LinkedList的Node节点

可以把每个Node节点理解为一个带有前后钩子的盒子,这个盒子里装着一个元素(item),通过前后两个钩子(prev和next)连接到前后的盒子(节点),形成一个双向链表的数据结构。

Node类是LinkedList的一个静态内部类,用于表示链表中的每个节点。
每个节点包含一个元素(item),指向前一个节点的引用(prev)和指向后一个节点的引用(next)。

private static class Node<E> {// 节点中存储的元素E item;// 指向下一个节点的引用Node<E> next;// 指向前一个节点的引用Node<E> prev;// Node类的构造函数,用于创建一个新节点// 参数prev是指向前一个节点的引用,element是当前节点中存储的元素,next是指向下一个节点的引用Node(Node<E> prev, E element, Node<E> next) {// 将传入的元素赋值给item,表示节点中存储的元素this.item = element;// 将传入的下一个节点引用赋值给nextthis.next = next;// 将传入的前一个节点引用赋值给prevthis.prev = prev;}
}

5、双向链表的概念和Node节点的详细解释

双向链表(Doubly Linked List)是一种链表数据结构。
其中每个节点包含三个主要部分:
存储的元素(数据);
指向前一个节点的引用(前驱);
指向后一个节点的引用(后继);

这种结构允许从任意节点向前或向后遍历链表,提供了比单向链表更灵活的操作。

在双向链表中,Node节点是链表的基本组成部分。每个节点都包含存储的元素以及指向前一个节点和后一个节点的引用。

Node节点的结构如下:

  • 元素(item):节点中存储的数据,这可以是任意类型的对象。
  • 前驱节点引用(prev):指向链表中前一个节点的引用。如果当前节点是链表的第一个节点,则这个引用为null。
  • 后继节点引用(next):指向链表中下一个节点的引用。如果当前节点是链表的最后一个节点,则这个引用为null。

单向链表和双向链表的图示:
在这里插入图片描述

6、LinkedList的add(E e)方法梳理

通过空参构造新建LinkedList实例
下面分析添加 “秀逗” 、“四眼” 两个元素的过程

LinkedList<String> list = new LinkedList<>();list.add("秀逗");list.add("四眼");

执行无参构造创建 LinkedList实例

// 用于跟踪修改次数的计数器,主要用于迭代器的快速失败(fail-fast)机制
protected transient int modCount = 0;// 链表中元素的数量
transient int size = 0;// 链表的第一个节点引用
transient Node<E> first;// 链表的最后一个节点引用
transient Node<E> last;
public LinkedList() {}

执行add方法 添加"秀逗"

public boolean add(E e) {// 调用linkLast方法将元素e添加到链表的末尾linkLast(e);// 始终返回true,表示元素添加成功return true;
}
  • 调用linkLast(E e)方法:这是将"秀逗"添加到链表末尾的具体实现方法。
void linkLast(E e) {// 将当前链表的最后一个节点引用保存到局部变量l中final Node<E> l = last;// 创建一个新的节点,节点的前驱是l(即当前的最后一个节点),// 节点包含的元素是e,后继节点为null(因为新节点将成为最后一个节点)final Node<E> newNode = new Node<>(l, e, null);// 更新链表的最后一个节点引用为新创建的节点last = newNode;// 检查链表是否为空(即当前没有任何节点)// 如果链表为空(即last为null),那么新节点也是第一个节点if (l == null)// 将链表的第一个节点引用也更新为新创建的节点first = newNode;else// 如果链表不为空(即已有节点),// 那么将当前最后一个节点的next指向新创建的节点l.next = newNode;// 链表的元素数量增加1size++;// 修改计数器增加1// 这个计数器在结构发生变化(例如添加或删除元素)时增加,// 用于实现迭代器的快速失败机制,检测并发修改modCount++;
}

下面把添加过程串起来

  • 1.保存当前最后一个节点的引用到局部变量 l:
    final Node<E> l = last; // 由于last在空参构造的初始化为null ,所以 l为null

  • 2.创建一个新的节点newNode:
    final Node<E> newNode = new Node<>(l, e, null);
    newNode的前驱节点为l(当前的最后一个节点) 值为null。
    newNode包含的元素为 “秀逗” 。
    newNode的后继节点为null(因为它将成为新的最后一个节点)。

  • 3.更新链表的最后一个节点引用为newNode:
    last = newNode; // 将last更新为newNode。

  • 4.检查链表是否为空:
    if (l == null) first = newNode;
    l 现在为空 ,说明这是链表中的第一个节点。
    将链表的第一个节点引用first也更新为newNode。

  • 5.增加链表的元素数量:
    将链表的大小size增加1。

  • 6.增加修改计数器modCount:
    用于实现迭代器的快速失败机制,检测并发修改。

    1. 再添加 “四眼” 这个元素
      保存当前最后一个节点的引用到局部变量 l:
      final Node<E> l = last; // 由于last在添加"秀逗"的时候已经赋值了
      所以这里 l 是有值的
  • 8.再创建一个新的节点newNode:
    final Node<E> newNode = new Node<>(l, e, null);
    newNode的前驱节点为l(当前的最后一个节点) 值为 “秀逗” 。
    newNode包含的元素为 “四眼” 。
    newNode的后继节点为null(因为它将成为新的最后一个节点)。

  • 9.再更新链表的最后一个节点引用为newNode:
    last = newNode; // 将last更新为newNode。 此时"四眼"就成了最后一个元素

  • 10.再检查链表是否为空:
    if (l == null) first = newNode; else l.next = newNode;
    l 现在为 “秀逗” ,不为空,讲 l 的下一个节点引用 next 指向 newNode 也就是 “四眼”。

这就完成了 “秀逗” , “四眼” 两个元素的 添加。

最终图示:
在这里插入图片描述

7、LinkedList的getXXX方法

LinkedList获取元素的方法有下面几个:
getFirst():获取链表的第一个元素。
getLast():获取链表的最后一个元素。
get(int index):获取链表指定位置的元素。

我在ArrayList详解里面并没有去讲解get方法,因为ArrayList中的get并没有什么好讲的。

// ArrayList中的get方法
public E get(int index) {// 先检查数组下标有没有越界rangeCheck(index);// 返回对应位置的元素return elementData(index);}private void rangeCheck(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}

但是LinkedList的getXXX方法是值得深入学一学的:
先看下最简单的两个 getFirst和getLast

public E getFirst() {// 获取链表的第一个节点引用,并将其保存在局部变量f中final Node<E> f = first;// 如果第一个节点为null,表示链表为空if (f == null)// 抛出NoSuchElementException异常,表示没有元素可以返回throw new NoSuchElementException();// 返回第一个节点中的元素return f.item;
}public E getLast() {// 获取链表的最后一个节点引用,并将其保存在局部变量l中final Node<E> l = last;// 如果最后一个节点为null,表示链表为空if (l == null)// 抛出NoSuchElementException异常,表示没有元素可以返回throw new NoSuchElementException();// 返回最后一个节点中的元素return l.item;
}

最值得去探究的是 get(int index):获取链表指定位置的元素。

我们知道LinkedList是链表结构,无法像ArrayList那样快速随机访问。那么LinkedList想根据下标去找到某个元素就只能靠遍历了。
看下具体是怎么实现的:

public E get(int index) {// 检查元素索引是否合法,如果不合法会抛出 IndexOutOfBoundsExceptioncheckElementIndex(index);// 获取指定索引的节点并返回其包含的元素return node(index).item;
}Node<E> node(int index) {// 判断索引在链表中的位置,以决定从头部还是尾部开始遍历// size >> 1  计算链表长度/2 向下取整  // 如果size是偶数 右移一位的结果就是size的一半 如果size是奇数 右移一位的结果就是size的一半再减0.5  if (index < (size >> 1)) {// 如果索引在前半部分,从头部开始遍历Node<E> x = first;// 通过不断访问 next 来找到指定索引的节点for (int i = 0; i < index; i++)x = x.next;return x;} else {// 如果索引在后半部分,从尾部开始遍历Node<E> x = last;// 通过不断访问 prev 来找到指定索引的节点for (int i = size - 1; i > index; i--)x = x.prev;return x;}
}

这里我做了个动画来演示查找的过程。

  • ①、从链表头部开始遍历
public static void main(String[] args){LinkedList<String> list = new LinkedList<>(Arrays.asList("1","2","秀逗","4","5","6","7","四眼","9","10"));String s = list.get(2);System.out.println(s);  // 秀逗
}

由于下标2 小于整个链表长度size 10右移一位 10 >> 1 = 5, 即2<5, 就从链表的头部开始遍历
在这里插入图片描述

  • ②、从链表尾部开始遍历
public static void main(String[] args){LinkedList<String> list = new LinkedList<>(Arrays.asList("1","2","秀逗","4","5","6","7","四眼","9","10"));String s = list.get(7);System.out.println(s);  // 四眼
}

由于下标7 大于整个链表长度size 10右移一位 10 >> 1 = 5, 即7>5, 就从链表的尾部开始遍历
在这里插入图片描述

8、LinkedList的removeXXX方法

LinkedList删除元素的方法有下面几个:

  • ①、removeFirst():删除并返回链表的第一个元素。
  • ②、removeLast():删除并返回链表的最后一个元素。
  • ③、remove(E e):删除链表中首次出现的指定元素,如果不存在该元素则返回 false。
  • ④、remove(int index):删除指定索引处的元素,并返回该元素的值。

①、removeFirst()方法 (移除LinkedList的第一个元素):

public E removeFirst() {// 获取链表的第一个节点,赋值给变量 ffinal Node<E> f = first;// 如果链表为空,即第一个节点为 null,抛出 NoSuchElementException 异常if (f == null)throw new NoSuchElementException();// 调用 unlinkFirst 方法,从链表中移除第一个节点return unlinkFirst(f);
}private E unlinkFirst(Node<E> f) {// 确保 f 是第一个节点且不为 null 的断言,注释掉的是因为在实际代码中没有执行断言检查的需要// assert f == first && f != null;// 保存第一个节点的值final E element = f.item;// 保存第一个节点的下一个节点final Node<E> next = f.next;// 将第一个节点的值设为 null,帮助垃圾回收f.item = null;// 将第一个节点的 next 指针设为 null,帮助垃圾回收f.next = null;// 更新链表的头节点为原头节点的下一个节点first = next;// 如果更新后的头节点为 null,说明链表现在为空,更新尾节点为 nullif (next == null)last = null;else// 如果更新后的头节点不为 null,将它的 prev 指针设为 nullnext.prev = null;// 链表长度减一size--;// 修改计数器增加一,用于记录链表结构的修改次数modCount++;// 返回被移除的节点的值return element;
}

总结一下:

  • 1.removeFirst() 方法:
    首先,获取链表的第一个节点,并检查其是否为 null。
    如果链表为空,则抛出 NoSuchElementException 异常。
    否则,调用 unlinkFirst(f) 方法从链表中移除第一个节点,并返回该节点的值。

  • 2.unlinkFirst(Node f) 方法:
    保存要移除的节点的值和它的下一个节点。
    将要移除节点的 item 和 next 设为 null,以帮助垃圾回收。
    更新链表的头节点为原头节点的下一个节点。如果新头节点为 null,说明链表现在为空,因此将尾节点也设为 null;否则,将新头节点的 prev 指针设为 null。
    减少链表的大小,并增加修改计数器。
    返回被移除节点的值。

②、removeLast()方法 (移除LinkedList的最后一个元素):

public E removeLast() {// 获取链表的最后一个节点,赋值给变量 lfinal Node<E> l = last;// 如果链表为空,即最后一个节点为 null,抛出 NoSuchElementException 异常if (l == null)throw new NoSuchElementException();// 调用 unlinkLast 方法,从链表中移除最后一个节点return unlinkLast(l);
}private E unlinkLast(Node<E> l) {// 确保 l 是最后一个节点且不为 null 的断言,注释掉的是因为在实际代码中没有执行断言检查的需要// assert l == last && l != null;// 保存最后一个节点的值final E element = l.item;// 保存最后一个节点的前一个节点final Node<E> prev = l.prev;// 将最后一个节点的值设为 null,帮助垃圾回收l.item = null;// 将最后一个节点的 prev 指针设为 null,帮助垃圾回收l.prev = null;// 更新链表的尾节点为原尾节点的前一个节点last = prev;// 如果更新后的尾节点为 null,说明链表现在为空,更新头节点为 nullif (prev == null)first = null;else// 如果更新后的尾节点不为 null,将它的 next 指针设为 nullprev.next = null;// 链表长度减一size--;// 修改计数器增加一,用于记录链表结构的修改次数modCount++;// 返回被移除的节点的值return element;
}

总结一下:

  • 1.removeLast() 方法:
    首先,获取链表的最后一个节点,并检查其是否为 null。
    如果链表为空,则抛出 NoSuchElementException 异常。
    否则,调用 unlinkLast(l) 方法从链表中移除最后一个节点,并返回该节点的值。

  • 2.unlinkLast(Node l) 方法:
    保存要移除的节点的值和它的前一个节点。
    将要移除节点的 item 和 prev 设为 null,以帮助垃圾回收。
    更新链表的尾节点为原尾节点的前一个节点。如果新尾节点为 null,说明链表现在为空,因此将头节点也设为 null;否则,将新尾节点的 next 指针设为 null。
    减少链表的大小,并增加修改计数器。
    返回被移除节点的值。

③、remove(E e)方法 (这个方法值得深入)

public boolean remove(Object o) {// 如果要移除的元素为 nullif (o == null) {// 从链表的第一个节点开始遍历for (Node<E> x = first; x != null; x = x.next) {// 如果当前节点的值为 nullif (x.item == null) {// 调用 unlink 方法移除当前节点unlink(x);// 返回 true 表示成功移除元素return true;}}} else {// 如果要移除的元素不为 null// 从链表的第一个节点开始遍历for (Node<E> x = first; x != null; x = x.next) {// 如果当前节点的值与要移除的元素相等if (o.equals(x.item)) {// 调用 unlink 方法移除当前节点unlink(x);// 返回 true 表示成功移除元素return true;}}}// 如果遍历整个链表没有找到要移除的元素,返回 falsereturn false;
}
E unlink(Node<E> x) {// 确保 x 不为 null 的断言,注释掉的是因为在实际代码中没有执行断言检查的需要// assert x != null;// 保存要移除节点的值final E element = x.item;// 保存要移除节点的下一个节点final Node<E> next = x.next;// 保存要移除节点的前一个节点final Node<E> prev = x.prev;// 如果要移除的节点是第一个节点if (prev == null) {// 更新链表的头节点为要移除节点的下一个节点first = next;} else {// 否则,将前一个节点的 next 指向要移除节点的下一个节点prev.next = next;// 将要移除节点的 prev 指针设为 null,帮助垃圾回收x.prev = null;}// 如果要移除的节点是最后一个节点if (next == null) {// 更新链表的尾节点为要移除节点的前一个节点last = prev;} else {// 否则,将下一个节点的 prev 指向要移除节点的前一个节点next.prev = prev;// 将要移除节点的 next 指针设为 null,帮助垃圾回收x.next = null;}// 将要移除节点的值设为 null,帮助垃圾回收x.item = null;// 链表长度减一size--;// 修改计数器增加一,用于记录链表结构的修改次数modCount++;// 返回被移除节点的值return element;
}

总结一下:
remove(Object o) 方法:
该方法用于从链表中移除第一个与给定元素 o 相等的节点。
如果 o 为 null,则遍历链表,找到第一个节点值为 null 的节点,并调用 unlink(x) 方法移除该节点,返回 true。
如果 o 不为 null,则遍历链表,找到第一个节点值与 o 相等的节点,并调用 unlink(x) 方法移除该节点,返回 true。
如果遍历完整个链表没有找到与 o 相等的节点,返回 false。

unlink(Node<E> x) 方法:
该方法用于从链表中移除指定的节点 x。
保存要移除节点的值、下一个节点和前一个节点。
如果要移除的节点是第一个节点,则更新链表的头节点为下一个节点;否则,将前一个节点的 next 指针指向下一个节点,并将要移除节点 的 prev 指针设为 null。
如果要移除的节点是最后一个节点,则更新链表的尾节点为前一个节点;否则,将下一个节点的 prev 指针指向前一个节点,并将要移除节点的 next 指针设为 null。
将要移除节点的值设为 null,以帮助垃圾回收。
链表的长度减一,并增加修改计数器。
返回被移除节点的值。

这里还是做个动画帮助理解 双向链表移除某个数据的过程:
在这里插入图片描述
可以看到最后
在这里插入图片描述
秀逗的pre 和 next 都被置为了null。
这样做可以确保该节点与链表完全断开连接,从而有助于垃圾回收机制回收该节点。

④、remove(int index)方法

这个方法 实际上在上面的分析中都提到了:
比如checkElementIndex、unlink、node方法在上面都有。
所以这个只给出方法注释 就不总结画图了。

public E remove(int index) {// 检查给定的索引是否在链表的有效范围内checkElementIndex(index);// 找到给定索引处的节点并移除它,返回被移除节点的值return unlink(node(index));
}
private void checkElementIndex(int index) {// 如果给定的索引不是有效的元素索引,抛出 IndexOutOfBoundsException 异常if (!isElementIndex(index))throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
Node<E> node(int index) {// 确保给定的索引是有效的元素索引的断言,注释掉的是因为在实际代码中没有执行断言检查的需要// assert isElementIndex(index);// 如果给定的索引在前半部分if (index < (size >> 1)) {Node<E> x = first;// 从头节点开始遍历,找到索引处的节点for (int i = 0; i < index; i++)x = x.next;return x;} else {// 如果给定的索引在后半部分Node<E> x = last;// 从尾节点开始反向遍历,找到索引处的节点for (int i = size - 1; i > index; i--)x = x.prev;return x;}
}
E unlink(Node<E> x) {// 确保 x 不为 null 的断言,注释掉的是因为在实际代码中没有执行断言检查的需要// assert x != null;// 保存要移除节点的值final E element = x.item;// 保存要移除节点的下一个节点final Node<E> next = x.next;// 保存要移除节点的前一个节点final Node<E> prev = x.prev;// 如果要移除的节点是第一个节点if (prev == null) {// 更新链表的头节点为要移除节点的下一个节点first = next;} else {// 否则,将前一个节点的 next 指向要移除节点的下一个节点prev.next = next;// 将要移除节点的 prev 指针设为 null,帮助垃圾回收x.prev = null;}// 如果要移除的节点是最后一个节点if (next == null) {// 更新链表的尾节点为要移除节点的前一个节点last = prev;} else {// 否则,将下一个节点的 prev 指向要移除节点的前一个节点next.prev = prev;// 将要移除节点的 next 指针设为 null,帮助垃圾回收x.next = null;}// 将要移除节点的值设为 null,帮助垃圾回收x.item = null;// 链表长度减一size--;// 修改计数器增加一,用于记录链表结构的修改次数modCount++;// 返回被移除节点的值return element;
}

最后还有个clear方法 也很有意思
也来看下 这可比 ArrayList的 clear方法麻烦多了

public void clear() {// 清除所有节点之间的链接是“非必要”的,但这样做有以下好处:// - 如果被丢弃的节点跨越多个垃圾分代,有助于分代的垃圾回收 (generational GC)// - 即使存在可达的迭代器,也能确保释放内存// 从链表的第一个节点开始for (Node<E> x = first; x != null; ) {// 保存当前节点的下一个节点Node<E> next = x.next;// 将当前节点的值设为 null,帮助垃圾回收x.item = null;// 将当前节点的 next 指针设为 null,帮助垃圾回收x.next = null;// 将当前节点的 prev 指针设为 null,帮助垃圾回收x.prev = null;// 移动到下一个节点x = next;}// 将链表的头节点和尾节点设为 null,表示链表为空first = last = null;// 将链表的大小设为 0size = 0;// 修改计数器增加一,用于记录链表结构的修改次数modCount++;
}

9、LinkedList的反向迭代器 DescendingIterator

DescendingIterator 可以从最后一个元素往前迭代。

public static void main(String[] args) throws Exception {LinkedList<String> list = new LinkedList<>(Arrays.asList("1","2","秀逗","4","5","6","7","四眼","9","10"));Iterator<String> iterator = list.iterator();while (iterator.hasNext()){System.out.print(iterator.next() + " ");}System.out.println("\n=============================");Iterator<String> descIterator = list.descendingIterator();while (descIterator.hasNext()){System.out.print(descIterator.next() + " ");}}

执行结果:

1 2 秀逗 4 5 6 7 四眼 9 10 
=============================
10 9 四眼 7 6 5 4 秀逗 2 1 

看下DescendingIterator 的实现:

// LinkedList的内部类,实现从尾部到头部的迭代器
private class DescendingIterator implements Iterator<E> {// 使用ListItr类的实例实现迭代器功能private final ListItr itr = new ListItr(size());// 检查是否有下一个元素(实际上是前一个元素,因为我们是反向迭代)public boolean hasNext() {// 调用ListItr的hasPrevious方法,检查是否有前一个元素return itr.hasPrevious();}// 获取下一个元素(实际上是前一个元素,因为我们是反向迭代)public E next() {// 调用ListItr的previous方法,返回前一个元素return itr.previous();}// 移除当前元素public void remove() {// 调用ListItr的remove方法,移除当前元素itr.remove();}
}

所以DescendingIterator 本质上 就是对ListItr 的包装 主要是实现细节都在 ListItr中
再看下 ListItr 的细节:
注意这个ListItr 是LinkedList的内部类 , 而不是 AbstractList抽象类里面的 内部类 ListItr (集合API内部类的命名就不能好好区分一下吗…)

// LinkedList的内部类,实现双向列表迭代器
private class ListItr implements ListIterator<E> {// 最近返回的节点private Node<E> lastReturned;// 下一个节点private Node<E> next;// 下一个节点的索引private int nextIndex;// 预期的修改计数,用于检测并发修改private int expectedModCount = modCount;// 构造函数,根据给定的索引初始化迭代器ListItr(int index) {// 如果索引等于列表大小,next为nullnext = (index == size) ? null : node(index);// 初始化下一个节点的索引nextIndex = index;}// 检查是否有下一个元素public boolean hasNext() {return nextIndex < size;}// 获取下一个元素public E next() {// 检查是否有并发修改checkForComodification();if (!hasNext())throw new NoSuchElementException();// 设置最后返回的节点为当前的下一个节点lastReturned = next;// 移动到下一个节点next = next.next;// 增加下一个节点的索引nextIndex++;// 返回最后返回节点的值return lastReturned.item;}// 检查是否有前一个元素public boolean hasPrevious() {return nextIndex > 0;}// 获取前一个元素public E previous() {// 检查是否有并发修改checkForComodification();if (!hasPrevious())throw new NoSuchElementException();// 如果next为null,说明当前在列表的末尾// 否则移动到前一个节点lastReturned = next = (next == null) ? last : next.prev;// 减少下一个节点的索引nextIndex--;// 返回最后返回节点的值return lastReturned.item;}// 获取下一个元素的索引public int nextIndex() {return nextIndex;}// 获取前一个元素的索引public int previousIndex() {return nextIndex - 1;}// 移除当前元素public void remove() {// 检查是否有并发修改checkForComodification();if (lastReturned == null)throw new IllegalStateException();// 获取最后返回节点的下一个节点Node<E> lastNext = lastReturned.next;// 从链表中取消链接最后返回的节点unlink(lastReturned);if (next == lastReturned)next = lastNext;elsenextIndex--;// 重置最后返回的节点lastReturned = null;// 更新预期的修改计数expectedModCount = modCount;}// 更新当前元素public void set(E e) {if (lastReturned == null)throw new IllegalStateException();checkForComodification();lastReturned.item = e;}// 添加元素public void add(E e) {checkForComodification();lastReturned = null;if (next == null)linkLast(e);elselinkBefore(e, next);nextIndex++;expectedModCount = modCount;}// 检查是否有并发修改的方法final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/25543.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Tomcat源码解析(八):一个请求的执行流程(附Tomcat整体总结)

Tomcat源码系列文章 Tomcat源码解析(一)&#xff1a;Tomcat整体架构 Tomcat源码解析(二)&#xff1a;Bootstrap和Catalina Tomcat源码解析(三)&#xff1a;LifeCycle生命周期管理 Tomcat源码解析(四)&#xff1a;StandardServer和StandardService Tomcat源码解析(五)&…

吴恩达神经网络学习笔记1

代码解释 并不是全部代码&#xff0c;思路的流程 import numpy as np# 如何判断咖啡豆是烤好了 # 假设此神经网络由2层构成###### 这部分代码只是如何建立2层网络&#xff0c; ###### 并不包含如何加载神经网络中的参数 w 和 b######################## 第1层网络# x 是…

Ruoyi5.x RuoYi-Vue-Plus新建Translation翻译类

若依框架&#xff08;RuoYi&#xff09;中的Translation翻译类主要作用在于实现字段值的转换或翻译功能&#xff0c;以提高数据展示的准确性和友好性。以下是其具体作用的一些关键点&#xff1a; 字段值转换&#xff1a;若依框架在处理数据时&#xff0c;有时需要将某些字段的…

CrawlSpace爬虫部署框架介绍

CrawlSpace爬虫部署框架介绍 全新的爬虫部署框架&#xff0c;为了适应工作的爬虫部署的使用&#xff0c;需要自己开发一个在线编写爬虫及部署爬虫的框架&#xff0c;框架采用的是Django2.2bootstap依赖scrapyd开发的全新通用爬虫在线编辑部署及scrapy项目的部署框架。项目实现的…

读AI未来进行式笔记08自主57

1. 自主57 1.1. 自主57被视为继火药、核57之后的“第三次zhan筝革命” 1.2. 虽然地雷和导弹揭开了早期简单自主57的序幕&#xff0c;但运用了AI技术的真正的自主57才是正片 1.2.1. AI自主57让整个sha戮过程&#xff1a;搜寻目标、进入zhan斗、抹sha生命&#xff0c;完全无须…

【Labview】通过串口通信从上位机读取和写入数据

最近博主需要通过Labview的上位机控制一个温控仪表&#xff0c;主要实现在上位机读取实时温度和设定的目标温度&#xff0c;以及通过上位机设定目标温度。这里将其中遇到的问题和心得分享给大家&#xff0c;博主自己也做一个记录。 由于温控仪表采用的485通讯&#xff0c;modb…

C语言——字符数组

一、字符数组的定义 语言字符数组的定义是指在C语言中可以使用一组连续的字符来存储和处理字符串。在定义字符数组时&#xff0c;需要指定数组的大小&#xff0c;并且可以初始化数组的内容。 1、字符数组方式&#xff1a; char str[] "Hello,world!";2、指针方式…

目标检测数据集 - 垃圾桶满溢检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;垃圾桶满溢检测数据集&#xff0c;真实场景高质量图片数据&#xff0c;涉及场景丰富&#xff0c;比如城市道边垃圾桶满溢、小区垃圾桶满溢、社区垃圾桶满溢、农村道边垃圾桶满溢、垃圾集中处理点垃圾桶满溢、公园垃圾桶满溢数据等。数据集标注标签划分为…

c++ 里函数选择的优先级:普通函数、模板函数、万能引用,函数重载的常量左值引用、右值引用,编译器选择哪个执行呢?

看大师写的代码时&#xff0c;除了在类里定义了 copy 构造函数&#xff0c;移动构造函数&#xff0c;还定义了对形参采取万能引用的构造函数&#xff0c;因此有个疑问&#xff0c;这时候的构造函数优先级是什么样的呢&#xff1f;简化逻辑测试一下&#xff0c;如下图&#xff0…

【Vue】项目创建目录初始化

文章目录 vue-cli 建项目调整初始化目录结构 vue-cli 建项目 1.安装脚手架 (已安装) npm i vue/cli -g2.创建项目 vue create hm-shopping选项 Vue CLI v5.0.8 ? Please pick a preset:Default ([Vue 3] babel, eslint)Default ([Vue 2] babel, eslint) > Manually sel…

new RegExp(Reg).test(value)无效

目录 前沿 问题分析 eval 函数 # 定义和用法 # 语法 # 浏览器支持 # 实例 使用eval函数 优化 拓展 —— 要么旅行&#xff0c;要么读书&#xff0c;身体和灵魂必须有一个在路上。 前沿 之前写过一篇正则表达式的基础&#xff1a;http://t.csdnimg.cn/45Da3 今天继…

张霖浩在娱乐“名利场”玩出“修罗场”的贵族范儿

众所周知娱乐圈是个大型“名利场”&#xff01;近日&#xff0c;2025年北京广播电视台春晚发布会现场&#xff0c;众大咖汇聚&#xff0c;妆容、装扮、穿搭&#xff0c;更是争奇斗艳、八仙过海各显神通。同时&#xff0c;也揭露出娱乐圈当下穿搭界”修罗场”的残酷现实。在出彩…

AI智能体的分级

技术的分级 人们往往通过对一个复杂的技术进行分级&#xff0c;明确性能、适用范围和价值&#xff0c;方便比较、选择和管理&#xff0c;提高使用效率&#xff0c;促进资源合理分配和技术改进和标准化。 比如&#xff0c;国际汽车工程师学会&#xff08;SAE&#xff09;定义了自…

2024年第三届数据统计与分析竞赛(B题)数学建模完整思路+完整代码全解全析

你是否在寻找数学建模比赛的突破点&#xff1f;数学建模进阶思路&#xff01; 详细请查 作为经验丰富的数学建模团队&#xff0c;我们将为你带来2024年第三届数据统计与分析竞赛&#xff08;B题&#xff09;的全面解析。这个解决方案包不仅包括完整的代码实现&#xff0c;还有…

排序题+贪心

排序力扣题 一&#xff1a;合并区间 56. 合并区间 方法一&#xff1a;先排序再合并 如图&#xff0c;把区间按照起点从小到达排序&#xff0c;如果起点相同那么按照终点小的优先排序 然后每次记录一个区间&#xff0c;访问下一个区间&#xff1a; 如果下一个区间的起点<前…

Vue TypeScript 实战:掌握静态类型编程

title: Vue TypeScript 实战&#xff1a;掌握静态类型编程 date: 2024/6/10 updated: 2024/6/10 excerpt: 这篇文章介绍了如何在TypeScript环境下为Vue.js应用搭建项目结构&#xff0c;包括初始化配置、创建Vue组件、实现状态管理利用Vuex、配置路由以及性能优化的方法&#x…

数据中心网络运维探讨

数据中心网络运维探讨 数据中心网络运维通过科学的网络架构设计、实时监控管理、智能化运维工具和全面的安全防护&#xff0c;确保网络的高效、安全运行。它不仅提升了运维效率和网络可靠性&#xff0c;还保障了业务的连续性和数据安全。随着技术的不断进步&#xff0c;智能化…

推测性解码:加速多模态大型语言模型的推理

大模型&#xff08;LLMs&#xff09;以其卓越的性能在多个应用场景中大放异彩。然而&#xff0c;随着应用的深入&#xff0c;这些模型的推理速度问题逐渐凸显。为了解决这一挑战&#xff0c;推测性解码&#xff08;Speculative Decoding, SPD&#xff09;技术应运而生。本文深入…

Vue 2看这篇就够了

Vue 2 技术文档 Vue.js 是一款用于构建用户界面的渐进式框架。与其他重量级框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或既有项目整合。而 Vue.js 2&#xff08;以下简称 Vue…

Vue2基础:.sync修饰符的使用,认识,作用,本质案例演示,实现父子之间的通信。

.sync的作用&#xff1a; 可以实现子组件与父组件数据的双向绑定&#xff0c;简化代码。 与v-model的不同点,prop属性名可以自定义&#xff0c;不要一定要用value. .sync的本质&#xff1a; 就是&#xff1a;属性名和update&#xff1a;属性名合写。 下面我们进行代码演示…