注意:
1.我们从上图中可以看出链表LinkedList继承于List接口:
如果不懂List接口的朋友们可以先看我上期作品了解一下List接口
2.本期注重讲解Java中LinkedList链表中个方法的使用:
如果不懂链表(LinkedList)的朋友们可以先看我这前的作品了解一下链表的讲解
数据结构(Linked List链表)_链表 列表-CSDN博客
那么正文开始
前言
在 Java 编程中,数据结构是编写高效、可维护代码的基础。而在众多数据结构中,链表(LinkedList
)作为一种重要的线性数据结构,常常在需要频繁插入和删除操作的场景中发挥重要作用。与基于数组实现的 ArrayList
相比,链表在执行插入和删除操作时具有明显的性能优势,尤其是在数据量较大的情况下。
Java 的 LinkedList
类是一个常用的双向链表实现,支持 List
、Deque
和 Queue
接口,提供了丰富的方法来操作链表元素。无论是在队列、栈的实现,还是在数据需要频繁修改的应用中,链表都能为我们提供更高效的解决方案。
本篇文章将详细介绍 Java 中 LinkedList
类的各种方法,从基础的元素添加、删除,到更高级的操作,如双向遍历和队列/栈功能等,帮助开发者深入理解链表的工作原理和在实际开发中的应用。
通过学习和掌握 LinkedList
提供的丰富 API,您可以在开发中根据不同的需求,选择最合适的数据结构,提高程序的性能和可扩展性。在今天的开发中,链表依然是许多高效算法和系统设计的核心组成部分,理解链表的使用和内部实现机制,对编写高效 Java 程序至关重要。
让我们一起深入探索 LinkedList
的各种方法及其应用,了解如何利用链表实现高效的数据操作。
1. 讲解JAVA链表中的方法
链表(LinkedList
)是一个非常重要的线性数据结构,它实现了 List
接口,支持在中间进行高效的插入和删除操作。LinkedList
是基于双向链表的,每个元素(节点)包含指向前一个节点和后一个节点的指针,从而支持双向遍历。
LinkedList
是 Java 集合框架中的一个类,继承自 AbstractSequentialList
,实现了 List
、Deque
和 Queue
接口。它使用双向链表结构来存储元素,因此在插入和删除元素时,不需要像 ArrayList
那样移动大量元素。
1. 添加元素的方法 (add
, offer
, addFirst
, addLast
)
1.1 add(E e)
- 功能:将指定元素添加到链表的末尾。
- 返回值:
true
(总是成功)。 - 示例
1.2 add(int index, E element)
- 功能:在指定的索引位置插入元素。如果索引大于当前链表的大小,元素会被添加到末尾。
- 返回值:无。
- 示例:
1.3 addFirst(E e)
- 功能:将元素插入到链表的头部(即最前面)。
- 返回值:无。
- 示例
1.4 addLast(E e)
- 功能:将元素添加到链表的尾部(即最后面)。
- 返回值:无。
- 示例:
1.5 offer(E e)
- 功能:将元素插入到链表的末尾。它与
add
方法类似,但通常用于队列操作。offer
在队列已满时返回false
,而add
会抛出异常。 - 返回值:
true
如果元素成功添加;false
如果添加失败。 - 示例:
2. 删除元素的方法 (remove
, poll
, removeFirst
, removeLast
)
2.1 remove(Object o)
- 功能:删除链表中首次出现的指定元素。如果链表中有多个相同的元素,只会删除第一个匹配的元素。
- 返回值:如果元素存在,返回
true
;否则返回false
。 - 示例:
2.2. remove(int index)
- 功能:删除指定索引位置的元素,并返回该元素。
- 返回值:被删除的元素。
- 示例:
2.3 removeFirst()
- 功能:删除链表头部的第一个元素。
- 返回值:返回被删除的元素。
- 示例:
2.4 removeLast()
- 功能:删除链表尾部的最后一个元素。
- 返回值:返回被删除的元素。
- 示例:
2.4 poll()
- 功能:从链表头部删除元素,并返回该元素。如果链表为空,返回
null
。 - 返回值:删除的元素;如果链表为空,返回
null
。 - 示例
2.5 pollFirst()
- 功能:删除并返回链表头部的第一个元素。如果链表为空,返回
null
。 - 返回值:删除的元素;如果链表为空,返回
null
。 - 示例:
2.6 pollLast()
- 功能:删除并返回链表尾部的最后一个元素。如果链表为空,返回
null
。 - 返回值:删除的元素;如果链表为空,返回
null
。 - 示例:
3. 访问元素的方法 (get
, peek
, getFirst
, getLast
)
3.1 get(int index)
- 功能:返回链表中指定索引位置的元素。
- 返回值:指定位置的元素。
- 示例
3.2 peek()
- 功能:获取链表头部的第一个元素,但不删除它。如果链表为空,返回
null
。 - 返回值:链表头部的元素;如果链表为空,返回
null
。 - 示例:
3.3 peekFirst()
- 功能:获取链表头部的第一个元素,但不删除它。如果链表为空,返回
null
。 - 返回值:链表头部的元素;如果链表为空,返回
null
。 - 示例
3.4 peekLast()
- 功能:获取链表尾部的最后一个元素,但不删除它。如果链表为空,返回
null
。 - 返回值:链表尾部的元素;如果链表为空,返回
null
。 - 示例:
3.5 getFirst()
- 功能:返回链表头部的第一个元素。如果链表为空,抛出
NoSuchElementException
异常。 - 返回值:链表头部的元素。
- 示例:
3.6 getLast()
- 功能:返回链表尾部的最后一个元素。如果链表为空,抛出
NoSuchElementException
异常。 - 返回值:链表尾部的元素。
- 示例:
4. 其他常用方法
size()
- 功能:返回链表中元素的数量。
- 返回值:链表中的元素数量。
- 示例:
4.2 clear()
- 功能:移除链表中的所有元素。
- 返回值:无。
- 示例:
4.3 contains(Object o)
- 功能:判断链表中是否包含指定的元素。
- 返回值:如果链表包含指定的元素,返回
true
;否则返回false
。 - 示例:
5. 链表转数组
1. toArray()
- 功能:将链表中的元素转换为一个对象类型的数组(
Object[]
)。 - 返回值:返回一个包含链表所有元素的数组。
2. toArray(T[] a)
- 功能:将链表中的元素转换为指定类型的数组。这个方法需要传入一个类型合适的数组作为参数。链表的元素将会被拷贝到该数组中。如果数组的大小不足,
toArray()
会自动创建一个足够大的新数组。 - 返回值:返回一个包含链表所有元素的指定类型的数组。
示例:
2.链表和顺序表的区别
1. 存储结构
链表:链表是一种由节点组成的线性数据结构,每个节点包含两个部分:
- 数据部分:存储实际的数据。
- 指针部分:指向下一个节点的地址(在双向链表中还会有一个指向前一个节点的指针)。
链表的节点通常分散在内存中,每个节点的地址是不连续的。节点之间通过指针连接在一起。
- 单向链表:每个节点指向下一个节点。
- 双向链表:每个节点不仅指向下一个节点,还指向前一个节点。
- 循环链表:最后一个节点指向头节点,形成一个环。
顺序表(数组):顺序表是基于数组实现的线性数据结构,所有元素按照顺序连续存储在内存中。每个元素可以通过索引(下标)快速访问。
顺序表在内存中是连续分配的,因此可以通过索引直接访问任意元素。
2. 插入与删除操作
链表:
- 插入:插入操作通常非常高效,尤其是在链表头部或尾部,时间复杂度是 O(1)(不需要移动元素)。在链表中间插入时,仍然是 O(1),但需要先找到目标位置(遍历链表),因此实际时间复杂度为 O(n)。
- 删除:删除操作同样很高效,删除链表的头部或尾部元素只需要 O(1) 时间,但删除中间节点时同样需要 O(n) 时间来定位该节点。
顺序表(数组):
- 插入:插入操作的时间复杂度为 O(n),因为在数组中插入一个新元素时,可能需要移动后面的所有元素以腾出空间(尤其是在数组中间插入)。插入到数组的末尾是 O(1),但这只能在数组大小允许的情况下。
- 删除:删除操作也需要移动元素,删除一个元素后,后面的所有元素需要向前移动,时间复杂度为 O(n)。如果删除的是最后一个元素,则可以 O(1) 删除。
3. 随机访问
链表:链表不支持快速随机访问。要访问链表的某个元素,必须从头节点或尾节点开始,逐个遍历节点,时间复杂度为 O(n)。
顺序表(数组):顺序表提供快速的随机访问,通过元素的索引可以在 O(1) 时间内访问任意位置的元素。
4. 内存空间
链表:链表需要额外的内存空间来存储指针(或引用),每个节点除了存储数据外,还需要存储一个或两个指针(在双向链表中)。这使得链表的内存开销比顺序表大。
顺序表(数组):顺序表的内存是连续的,不需要额外存储指针,因此相对于链表而言,其内存开销较小。只是需要确保数组大小足够容纳所有元素,否则需要进行扩容(通常是数组大小的两倍)。
5. 空间分配与扩展
链表:链表的节点在内存中是动态分配的,因此链表可以灵活地扩展和收缩,无需预分配固定大小的内存。
顺序表(数组):顺序表的内存是静态分配的,在创建时必须指定大小。如果元素个数超过数组的容量,就需要进行扩容。扩容通常是通过创建一个更大的数组并将原有元素复制到新数组中,这个过程是耗时的,时间复杂度是 O(n)。
6. 性能对比
操作 | 链表(LinkedList ) |
顺序表(ArrayList ) |
---|---|---|
插入操作 | 在头部/尾部 O(1),中间 O(n) | 在末尾 O(1),中间 O(n) |
删除操作 | 在头部/尾部 O(1),中间 O(n) | 在末尾 O(1),中间 O(n) |
访问操作 | O(n)(需要遍历) | O(1)(直接通过索引访问) |
空间效率 | 存储指针,较高的内存开销 | 存储数据,无指针开销 |
内存分配 | 动态分配,每个节点独立分配 | 静态分配,需要扩容 |
适用场景 | 频繁插入/删除的场景(队列/栈) | 频繁随机访问的场景 |
7. 应用场景对比
链表的优势:
- 频繁插入和删除:链表在插入和删除元素时具有 O(1) 的时间复杂度,尤其是在链表头部或尾部,因此适合用于需要频繁插入和删除的场景(如队列、栈等)。
- 动态变化的元素:链表在内存中不需要连续存储,因此在不知道数据大小的情况下,链表可以根据需要动态调整大小。
顺序表(数组)的优势:
- 快速随机访问:顺序表提供 O(1) 的随机访问能力,适合需要频繁访问任意位置数据的场景。
- 空间效率:顺序表比链表的内存开销小,没有额外的指针存储空间,适合数据量较大且不需要频繁插入/删除操作的场景。
8. 总结
链表:
- 适合频繁插入和删除操作的场景。
- 不适合频繁的随机访问。
- 内存开销相对较大(需要存储指针)。
- 适用于队列、栈等需要动态增长的应用场景。
顺序表(数组):
- 适合需要频繁随机访问元素的场景。
- 插入和删除操作性能较差,尤其是中间插入/删除。
- 内存开销较小,但需要在扩容时进行操作。
- 适用于需要快速随机访问、存储大量数据的场景(如查找、排序等)。
结语
链表作为一种基本的线性数据结构,在 Java 中的应用广泛且重要,尤其在频繁进行插入和删除操作的场景中,它能够提供比数组更高效的解决方案。Java 提供的 LinkedList
类,作为双向链表的实现,拥有丰富的 API 方法,能够方便地处理元素的插入、删除、查询等操作。
在本文中,我们深入探讨了 LinkedList
类 的各种方法,涵盖了链表元素的增删查改,访问元素、队列操作等常见功能。同时,我们还介绍了如何将链表转换为数组,这对于一些特定场景(如需要数组操作或者与其他数据结构兼容时)非常有用。
链表的优势在于其灵活的内存管理和高效的元素插入/删除操作,尤其在头部和尾部的操作上表现优异。尽管链表在随机访问元素时的效率低于数组,但它的优点使其在许多应用中仍然占据重要地位。了解并掌握链表的特性和操作方法,对于提高代码的性能和可扩展性至关重要。
随着对 LinkedList
的深入理解,你将能更好地选择合适的数据结构来应对不同的编程挑战。在开发过程中,合理利用 LinkedList
和其他数据结构(如 ArrayList
、HashMap
等),能够帮助你优化性能、提升代码的可维护性。
希望本文能帮助你更好地理解链表的使用与实现,掌握 LinkedList
类提供的各种方法,并在实际项目中充分发挥其优势。如果你有更多的问题或思考,欢迎与我交流与讨论。