LinkedList 是 Java 集合框架中 List 接口和 Deque 接口的双向链表实现。与 ArrayList 基于数组的实现不同,LinkedList 使用链表数据结构存储元素。
基本特性
-
实现结构:双向链表(每个节点包含前驱和后继指针)
-
线程安全:非线程安全(需要外部同步)
-
允许元素:允许 null 元素
-
实现接口:
-
List
接口 -
Deque
接口(双端队列操作) -
Queue
接口(队列操作)
-
性能特点
操作 | 时间复杂度 | 说明 |
---|---|---|
插入/删除头部 | O(1) | 直接修改头节点指针 |
插入/删除尾部 | O(1) | 直接修改尾节点指针 |
插入/删除中间 | O(n) | 需要遍历到指定位置 |
随机访问 | O(n) | 需要从头或尾遍历 |
搜索元素 | O(n) | 需要遍历整个链表 |
对比ArrayList
特性 | LinkedList | ArrayList |
---|---|---|
底层结构 | 双向链表 | 动态数组 |
随机访问 | 慢(O(n)) | 快(O(1)) |
头部插入/删除 | 快(O(1)) | 慢(O(n)) |
尾部插入/删除 | 快(O(1)) | 快(通常O(1)) |
中间插入/删除 | 慢(O(n)) | 慢(O(n)) |
内存占用 | 较高(每个元素需要额外指针) | 较低(仅存储数据) |
-
适合使用 LinkedList 的情况:
-
需要频繁在头部/尾部进行插入删除操作
-
需要实现栈、队列或双端队列功能
-
不确定数据量大小,需要动态增长
-
-
不适合使用 LinkedList 的情况:
-
需要频繁随机访问元素
-
内存资源紧张
-
需要线程安全集合(需外部同步或使用CopyOnWriteArrayList)
-
算法优化
1.获取元素
先看一下 Node<E> node(int index) 方法,此处先有个印象,后面很多地方会用。由于随机检索是要遍历链表,此处有一个小优化点,判断要查找的索引在链表中的位置,如果在前半部分则从前向后遍历,否则从后向前。
// 获取指定位置的元素
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
// 内部方法:定位到指定位置的节点
Node<E> node(int 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;
}
}
2. 添加元素
在链表尾部添加元素
// 在链表尾部添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
// 在指定位置插入元素
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
3. 删除元素
是通过遍历链表查询元素,在进行删除,效率差。
// 删除指定位置的元素
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
// 删除第一个出现的指定元素
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
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;
}
4. Deque 接口实现
栈,后进先出
包含下面方法
push(E e) // 压入栈顶元素,相当于在链表最头添加元素。
pop() // 弹出栈顶元素,相当于获取并移除头元素。
队列, 先进先出
boolean offer(E e) // 在链表尾添加元素。
E poll() // 获取并删除头元素。
E peek() // 获取头元素
// 作为栈使用(后进先出)
public void push(E e) { addFirst(e); }
public E pop() { return removeFirst(); }
// 作为队列使用(先进先出)
public boolean offer(E e) { return add(e); }
public E poll() { return (size == 0) ? null : removeFirst(); }
public E peek() { return (size == 0) ? null : getFirst(); }