一、LinkedList
1、LinkedList的概念
LinkedList 是一个基于双向链表实现的集合类,它允许包含所有元素(包括 null),并且支持在列表的两端进行高效的插入和删除操作,但查找删除慢(需要从头/尾往中间找)。
这里用一个比喻通俗的理解一下:做游戏时每个人手拉手站成一排(LinkedList)。每个人都知道自己的左边和右边是谁(双向链表)。如果想找到队伍中的第5个人,就必须从头开始一个个数到第5个,因为LinkedList是没有下标的(查找删除慢)。但是如果有人要离开队伍,只需要让他的左右两个人重新牵手就行了;如果有人要插队,也只需要在他要插入的位置,让左右两个人松手,然后和新来的人牵手(高效的插入和删除操作)。
2、LinkedList的底层逻辑
LinkedList的底层不是数组(所以无下标),而是一串节点(Node)相互链接,每个节点有三个部分:(1)数据(item);(2)指向前驱节点的引用(prev);(3)指向后继节点的引用(next);
JDK源码中的内部静态类:
ini
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 内部维护两个指针:first(头结点)和 last(尾结点),用于快速找到链子两端。这样的话,如果要在头部或尾部插入/删除,只需要改一下这两个指针,不用遍历整个链子。
java
transient int size = 0; // 当前元素个数
transient Node<E> first; // 头结点
transient Node<E> last; // 尾结点
3、LinkedList的列表代码实战
LinkedList基本概念就讲到这里,让我们用代码实战一下吧!
代码示例:
csharp
import java.util.LinkedList;
public class LinkedListAsListDemo {
public static void main(String[] args) {
// 1. 创建一个 LinkedList(当作顺序表)
LinkedList<String> list = new LinkedList<>();
// 增(Add)---------------------------------------------------
list.add("A"); // 在末尾添加
list.add("B");
list.add("C");
list.add(1, "D"); // 在下标 1 的位置插入元素
System.out.println("添加后的列表: " + list);
// 查(Read)---------------------------------------------------
String first = list.get(0); // 通过下标读取
String second = list.get(1);
System.out.println("第一个元素: " + first);
System.out.println("第二个元素: " + second);
// 改(Update)---------------------------------------------------
list.set(2, "E"); // 修改下标为 2 的元素
System.out.println("修改后的列表: " + list);
// 删(Delete)---------------------------------------------------
list.remove(1); // 删除下标为 1 的元素
list.remove("C"); // 删除指定对象(第一次出现的)
System.out.println("删除后的列表: " + list);
// 遍历(for-each)
System.out.print("遍历列表: ");
for (String s : list) {
System.out.print(s + " ");
}
}
}
运行结果:
说明:代码中使用了0、1等下标,比如list.set(2, "E"),这个里面的"2"是index;"E"是item。这并不是错误,因为LinkedList实现了List接口,而List接口定义了按index(下标)操作的方法,所以LinkedList也需要遵循这种方法。
List接口中index的定义:
arduino
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
比如当在代码中调用list.get(3)的时候,并不是像ArrayList那样通过O(1)直接拿到,而是需要判断 index 靠近头还是尾,然后选择近的一端开始遍历,直到找到目标 index 对应的节点。这个操作的底层逻辑还是遍历链表,并不是数组的随机访问。
4、LinkedList实现的接口
LinkedList实现了 List、Deque、Queue 接口,所以既能当顺序表用,又能当队列/栈用。
接口源码示例:
csharp
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
// ...
}
说明:
(1)implements List:支持按索引的增删查改(把它当顺序表用)。
(2)implements Deque:提供双端队列接口(左右两端的入队/出队/查看),Deque 继承自 Queue,因此也满足 Queue 的一切语义。
(3)底层为 Node(含 item, next, prev),并维护 first 和 last 两个引用(链表两端)。
这个先暂时分享到这里,等学习到队列和栈的时候在进行详细说明。
二、LinkedList和ArrayList的用法对比
特点 | ArrayList | LinkedList |
底层结构 | 动态数组 | 双向链表 |
随机访问(get / set) | 快,O(1) | 慢,O(n) |
插入/删除(中间位置) | 慢,要移动元素 O(n) | 快,只改指针 O(1) |
插入/删除(末尾 add/removeLast) | 快,均摊 O(1) | 快,O(1) |
内存占用 | 小(只存数据) | 大(额外存指针) |
适用场景 | 读多写少、随机访问多 | 写多读少、频繁插入删除 |
ArrayList 适合:频繁访问元素(比如查找、遍历),内存紧张场景。
LinkedList 适合:频繁在中间插入/删除元素,不太在乎访问速度的场景。
因为ArrayList和LinkedList都实现了List接口,所以在编写代码时用的API几乎是一样的,下面举出一个代码实例 :
csharp
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ListCompareDemo {
public static void main(String[] args) {
// ArrayList 示例
List<String> arrayList = new ArrayList<>();
arrayList.add("A");
arrayList.add("B");
arrayList.add("C");
// LinkedList 示例
List<String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.add("B");
linkedList.add("C");
// 常见操作
System.out.println("ArrayList 第一个元素: " + arrayList.get(0));
System.out.println("LinkedList 第一个元素: " + linkedList.get(0));
arrayList.remove(1); // 删除 index=1 的元素
linkedList.remove(1);
System.out.println("ArrayList 剩余: " + arrayList);
System.out.println("LinkedList 剩余: " + linkedList);
}
}
运行结果:
今天的学习就到这里啦,有什么不足还请指正!下期将会学习队列和栈。