一、LinkedList基础知识点
(一)基本概念
LinkedList 是 Java 中一个非常重要的数据结构,属于 java.util
包中的类。它是基于双向链表实现的,既可以作为列表(List)使用,也可以作为队列(Queue)或双端队列(Deque)使用。
(二)链表结构介绍
链表是由一系列非连续的节点组成的存储结构。链表又分为单向链表和双向链表,而单向/双向链表又可以分为循环链表和非循环链表。
- 单向链表:每个结点由数据项和指向下一个结点的指针构成,最后一个节点的
next
指向null
。 - 单向循环链表:和单向列表的不同是,最后一个节点的
next
不是指向null
,而是指向head
节点,形成一个“环”。 - 双向链表:包含两个指针,
pre
指向前一个节点,next
指向后一个节点,但是第一个节点head
的pre
指向null
,最后一个节点的tail
指向null
。 - 双向循环链表:和双向链表的不同在于,第一个节点的
pre
指向最后一个节点,最后一个节点的next
指向第一个节点,也形成一个“环”。而 LinkedList 就是基于双向循环链表设计的。
(三)LinkedList特点
- 数据重复性:数据可重复。
- 数据有序性:保证插入数据的有序性。
- null值问题:可以有多个
null
值。 - 底层数据结构:底层提供的是双向链表。其节点结构如下:
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; } }
- 继承关系:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
。LinkedList 直接继承了AbstractSequentialList
类,该类继承了AbstractList
类。LinkedList 实现了List
接口,Deque
接口,可以克隆,可以进行序列化和反序列化操作。 - 基本属性:
transient int size = 0; // 表示当前集合存储数据个数 transient Node<E> first; // 第一个节点 transient Node<E> last; // 最后一个节点 private static final long serialVersionUID = 876323262645176354L; // 用于 Java 的序列化机制 protected transient int modCount = 0; // 表示的是版本控制,添加,删除,修改之类的操作都会进行 ++ 操作
- 构造函数:
// 无参构造函数,构造一个空列表 public LinkedList() { } // 使用集合的构造方法,先构造一个空列表,再将集合中所有元素添加到列表中 public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
(四)常用方法
- 添加元素:
linkedList.add("Element"); // 在末尾添加元素 linkedList.addFirst("First Element"); // 在开头添加元素 linkedList.addLast("Last Element"); // 在末尾添加元素
- 获取元素:
E firstElement = linkedList.getFirst(); // 获取头部元素 E lastElement = linkedList.getLast(); // 获取尾部元素 E elementAtIndex = linkedList.get(index); // 根据索引获取元素
- 删除元素:
linkedList.remove(); // 删除第一个元素并返回 linkedList.remove(index); // 删除指定索引位置的元素并返回 linkedList.remove(Object o); // 删除第一个匹配的元素
二、JDK8之前LinkedList的相关特性和使用情况
(一)JDK1.6及之前
- 底层结构:使用的是一个带有头结点的双向循环链表,头结点不存储实际数据。通过一个
header
头指针保存队列头和尾。
private transient Entry<E> header = new Entry<E>(null, null, null); public LinkedList() { header.next = header.previous = header; }
- 增删改查方法:增删改查方法主要依赖
addBefore
和remove
等方法,由于有头结点,方法操作的一致性高,流程中if
判断比较少。
(二)JDK1.7
- 底层结构:使用不带头结点的普通的双向链表,增加两个节点指针
first
、last
分别指向首尾节点。
transient int size = 0; transient Node<E> first; transient Node<E> last; public LinkedList() { }
- 增删改查方法:变成了
linkBefore
、unlink
等方法,因为没有header
节点,需要用if
判断的就多一些,总共写了linkBefore
、linkFirst
、linkedLast
、unlink
、unlinkFirst
、unlinkLast
这 6 个方法。
(三)JDK8之前的使用场景
- 适合场景:频繁插入和删除操作,因为删除不会发生移位;可以实现队列、栈等数据结构;允许元素重复且非线程安全;维护了元素插入时的顺序。
- 不适合场景:随机访问元素效率低,因为需要从头或尾开始遍历链表。
三、JDK8之后LinkedList的改动和新特性
(一)底层结构
JDK8 之后,LinkedList 仍然使用不带头结点的普通双向链表,和 JDK1.7 保持一致。但在一些操作上有小的优化,例如在 add
、get
等方法中,会先判断 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; }
(二)新方法支持 Stream API
Java 8 为集合框架中添加了 stream()
和 parallelStream()
方法,支持流式处理集合中的数据。
import java.util.LinkedList; import java.util.List; public class LinkedListStreamExample { public static void main(String[] args) { List<String> names = new LinkedList<>(); names.add("张三"); names.add("李四"); names.add("王五"); names.stream() .filter(name -> name.startsWith("张")) .forEach(System.out::println); } }
(三)使用 List.sort()
方法对 LinkedList 进行排序
- 默认排序(自然顺序):
List.sort()
方法使用元素的自然顺序对其进行排序,即按升序排列,前提是这些元素实现了Comparable
接口。
import java.util.LinkedList; import java.util.List; public class DefaultSortExample { public static void main(String[] args) { List<Integer> list = new LinkedList<>(); list.add(5); list.add(2); list.add(8); list.add(1); list.add(3); list.sort(null); // null 表示使用元素的自然顺序 System.out.println("按自然顺序排序后的列表: " + list); } }
- 使用自定义
Comparator
进行排序:如果需要按照自定义的规则对 LinkedList 进行排序,可以传递一个Comparator
给List.sort()
方法。
import java.util.LinkedList; import java.util.List; import java.util.Comparator; public class CustomSortExample { public static void main(String[] args) { List<Integer> list = new LinkedList<>(); list.add(5); list.add(2); list.add(8); list.add(1); list.add(3); list.sort(Comparator.reverseOrder()); // 降序排序 System.out.println("按降序排序后的列表: " + list); } }
四、JDK8前后的详细对比
(一)底层结构对比
- JDK8之前(以JDK1.6和JDK1.7为例):JDK1.6 使用带有头结点的双向循环链表,JDK1.7 使用不带头结点的普通双向链表。
- JDK8之后:继续沿用 JDK1.7 的不带头结点的普通双向链表结构,但在一些操作上有小的优化,如根据
index
位置选择遍历方向。
(二)增删改查方法对比
- JDK8之前:JDK1.6 主要依赖
addBefore
和remove
等方法,JDK1.7 变成了linkBefore
、unlink
等方法。 - JDK8之后:方法基本保持一致,但在查找节点时会根据
index
位置优化遍历方向,提高一定的查找效率。
(三)新特性对比
- JDK8之前:主要提供基本的增删改查和队列、栈等操作。
- JDK8之后:支持 Stream API 进行流式处理,提供
List.sort()
方法进行排序,增强了代码的可读性和灵活性。