LinkedList
LinkedList
是 Java 集合框架中最灵活的数据结构之一,基于的 双向链表
实现,它同时实现了 List
和 Deque
和 Queue
接口,因此既可以作为List列表使用,也可以作为 队列(Queue)
或 双端队列(Deque)
使用。
核心特性
双向链表结构(双端队列)
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;
}
}
如果单从数据结构看是属于双向链表(prev和next),对于队首和队尾的操作但是O(1),但是LinkedList实现了三个接口:列表 基本队列 双端队列,所以LinkedList三种都可以使用
多接口实现
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{}
实现的接口列表:
List
:有序集合,支持索引访问Deque
:双端队列操作Queue
:队列操作(FIFO)
继承的抽象类AbstractSequentialList
1.继承AbstractList类,AbstractSequentialList为顺序访问的List结构提供了抽象父类,其子类为LinkedList
public abstract class AbstractSequentialList<E> extends AbstractList<E> {}
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>
2.AbstractSequentialList要求子类必实现size()、listIterator(int index)
方法
//来源从AbstractCollection
public abstract int size();
//来源自AbstractList
public abstract ListIterator<E> listIterator(int index);
3.AbstractSequentialList中有个抽象方法listIterator
public abstract ListIterator listIterator(int index)
4.AbstractSequentialList还提供了get(int index)、set(int index, E element)、add(int index, E element)、remove(int index)等方法的默认通用实现
// 示例其中一个get方法实现
public E get(int index) {
try {
return listIterator(index).next();
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
// ...................
动态扩容或收缩
扩容机制(添加元素)
1.无需预先指定容量
,初始队列空,大小从开始
2.调整相邻节点的引用关系
3.更新头/尾节点引用(如果需要)
4.增加 size 计数
搞一个printListState 实时打印链表内部结构
private static void printListState(LinkedList<String> list, String operation) {
System.out.println("\n===== 操作: " + operation + " =====");
System.out.println(operation+"后大小: " + list.size());
if (list.isEmpty()) {
System.out.println("头节点 -> NULL");
System.out.println("尾节点 -> NULL");
System.out.println("链表结构: [空]");
return;
}
// 打印头尾指针
System.out.println("头节点 -> " + list.getFirst());
System.out.println("尾节点 -> " + list.getLast());
// 构建链表结构可视化
StringBuilder structure = new StringBuilder();
// 添加头部标记
structure.append("Head -> ");
// 遍历所有节点
for (int i = 0; i < list.size(); i++) {
structure.append("[").append(list.get(i)).append("]");
// 添加节点连接 展示结构
if (i < list.size() - 1) {
structure.append(" ⇄ ");
}
}
// 添加尾部标记
structure.append(" <- Tail");
System.out.println("链表结构: " + structure);
System.out.println("节点详情:");
// 打印每个节点的前后关系
for (int i = 0; i < list.size(); i++) {
String prev = (i == 0) ? "NULL" : "[" + list.get(i - 1) + "]";
String next = (i == list.size() - 1) ? "NULL" : "[" + list.get(i + 1) + "]";
System.out.printf("节点 [%s]: prev=%s, next=%s%n",
list.get(i), prev, next);
}
}
LinkedList<String> list = new LinkedList<>();
System.out.println(list.size());
printListState(list, "初始空链表大小");
// 添加元素 (扩容)
list.add("A");
printListState(list, "添加 A");
list.add("B");
printListState(list, "添加 B");
list.addFirst("X");
printListState(list, "头部添加 X");
list.add(2, "Y");
printListState(list, "索引 2 处添加 Y");
打印
===== 操作: 初始空链表大小:0 =====
初始空链表大小:0后大小: 0
头节点 -> NULL
尾节点 -> NULL
链表结构: [空]
===== 操作: 添加 A =====
添加 A后大小: 1
头节点 -> A
尾节点 -> A
链表结构: Head -> [A] <- Tail
节点详情:
节点 [A]: prev=NULL, next=NULL
===== 操作: 添加 B =====
添加 B后大小: 2
头节点 -> A
尾节点 -> B
链表结构: Head -> [A] ⇄ [B] <- Tail
节点详情:
节点 [A]: prev=NULL, next=[B]
节点 [B]: prev=[A], next=NULL
===== 操作: 头部添加 X =====
头部添加 X后大小: 3
头节点 -> X
尾节点 -> B
链表结构: Head -> [X] ⇄ [A] ⇄ [B] <- Tail
节点详情:
节点 [X]: prev=NULL, next=[A]
节点 [A]: prev=[X], next=[B]
节点 [B]: prev=[A], next=NULL
===== 操作: 索引 2 处添加 Y =====
索引 2 处添加 Y后大小: 4
头节点 -> X
尾节点 -> B
链表结构: Head -> [X] ⇄ [A] ⇄ [Y] ⇄ [B] <- Tail
节点详情:
节点 [X]: prev=NULL, next=[A]
节点 [A]: prev=[X], next=[Y]
节点 [Y]: prev=[A], next=[B]
节点 [B]: prev=[Y], next=NULL
通过节点Node添加链表关系进行扩容,初始队列为空
收缩机制(删除元素)
1.断开目标节点的前后连接
2.将目标节点的前后引用设为 null(帮助 GC)
3.更新头/尾节点引用(如果需要)
4.减少 size 计数
// 删除元素 (收缩)
list.remove("B");
printListState(list, "删除 B");
list.removeFirst();
printListState(list, "删除头部");
list.removeLast();
printListState(list, "删除尾部");
// 清空链表
list.clear();
printListState(list, "清空链表");
打印输出
===== 操作: 删除 B =====
删除 B后大小: 3
头节点 -> X
尾节点 -> Y
链表结构: Head -> [X] ⇄ [A] ⇄ [Y] <- Tail
节点详情:
节点 [X]: prev=NULL, next=[A]
节点 [A]: prev=[X], next=[Y]
节点 [Y]: prev=[A], next=NULL
===== 操作: 删除头部 =====
删除头部后大小: 2
头节点 -> A
尾节点 -> Y
链表结构: Head -> [A] ⇄ [Y] <- Tail
节点详情:
节点 [A]: prev=NULL, next=[Y]
节点 [Y]: prev=[A], next=NULL
===== 操作: 删除尾部 =====
删除尾部后大小: 1
头节点 -> A
尾节点 -> A
链表结构: Head -> [A] <- Tail
节点详情:
节点 [A]: prev=NULL, next=NULL
===== 操作: 清空链表 =====
清空链表后大小: 0
头节点 -> NULL
尾节点 -> NULL
链表结构: [空]
多接口实现
非线程安全
- 多线程环境下需要外部同步
性能特点
操作 | 时间复杂度 | 说明 |
---|---|---|
头部插入/删除 | O(1) | 直接操作头节点 |
尾部插入/删除 | O(1) | 直接操作尾节点 |
指定位置插入/删除 | O(n) | 需要遍历到目标位置 |
随机访问 (get/set) | O(n) | 需要从头/尾遍历 |
搜索元素 (indexOf) | O(n) | 必须遍历整个列表 |
链表节点核心处理逻辑
新增/插入节点核心逻辑:
linkFirst(E e):链表首 新增节点
private void linkFirst(E e) {
// 1. 保存当前头节点
final Node<E> f = first;
// 2. 创建新节点(前驱为null,后继为当前头节点)
final Node<E> newNode = new Node<>(null, e, f);
// 3. 更新头指针
first = newNode; // 新节点成为头节点
// 4. 处理链表为空的情况
if (f == null) {
last = newNode; // 新节点也是尾节点
} else {
f.prev = newNode; // 原头节点的prev指向新节点
}
// 5. 更新链表状态
size++;
modCount++;
}
linkLast(E e):链表尾 新增节点
void linkLast(E e) {
final Node<E> l = last; // 当前尾节点
final Node<E> newNode = new Node<>(l, e, null); // 创建新节点
last = newNode; // 更新尾节点为新节点
if (l == null) {
first = newNode; // 如果链表为空,新节点也是头节点
} else {
l.next = newNode; // 否则将原尾节点的next指向新节点
}
size++; // 更新链表大小
modCount++; // 修改计数器
}
linkBefore(E e, Node succ): 指定节点之前插入新元素
指定节点succ
之前插入新元素e
void linkBefore(E e, Node<E> succ) {
final Node<E> pred = succ.prev; // 目标节点的前驱节点
final Node<E> newNode = new Node<>(pred, e, succ); // 创建新节点
succ.prev = newNode; // 目标节点的前驱指向新节点
if (pred == null) {
first = newNode; // 如果前驱为空,新节点成为头节点
} else {
pred.next = newNode; // 否则前驱节点的next指向新节点
}
size++; // 更新链表大小
modCount++; // 修改计数器
}
删除/移除节点核心逻辑:
unlink(Node x): 移除指定节点
E unlink(Node<E> x) {
// 1. 保存被移除节点的值
final E element = x.item;
// 2. 获取相邻节点
final Node<E> next = x.next;
final Node<E> prev = x.prev;
// 3. 处理前驱节点链接
if (prev == null) {
first = next; // x 是头节点
} else {
prev.next = next; // 前驱节点指向后继节点
x.prev = null; // 断开与前驱节点的链接
}
// 4. 处理后继节点链接
if (next == null) {
last = prev; // x 是尾节点
} else {
next.prev = prev; // 后继节点指向前驱节点
x.next = null; // 断开与后继节点的链接
}
// 5. 清理节点数据
x.item = null; // 帮助垃圾回收
// 6. 更新链表状态
size--;
modCount++;
return element;
}
unlinkFirst(Node f): 移除 链表头节点
private E unlinkFirst(Node<E> f) {
// 1. 保存头节点的值
final E element = f.item;
// 2. 获取头节点的下一个节点
final Node<E> next = f.next;
// 3. 清理当前头节点(帮助GC)
f.item = null;
f.next = null; // 断开与链表的连接
// 4. 更新头指针
first = next;
// 5. 处理链表只有一个元素的情况
if (next == null) {
last = null; // 链表变为空
} else {
next.prev = null; // 新头节点的prev置空
}
// 6. 更新链表大小和修改计数
size--;
modCount++;
// 7. 返回被移除的元素值
return element;
}
unlinkLast(Node l): 移除 链表尾节点
unlinkLast()
是 LinkedList
内部用于移除尾节点
private E unlinkLast(Node<E> l) {
// 1. 保存尾节点的值
final E element = l.item;
// 2. 获取尾节点的前驱节点
final Node<E> prev = l.prev;
// 3. 清理当前尾节点
l.item = null; // 释放数据引用
l.prev = null; // 断开前向链接
// 4. 更新尾指针
last = prev; // 新的尾节点
// 5. 处理链表只有一个元素的情况
if (prev == null) {
first = null; // 链表变为空
} else {
prev.next = null; // 新尾节点的next置空
}
// 6. 更新链表状态
size--;
modCount++;
// 7. 返回被移除的元素值
return element;
}
LinkedList内部操作方法
新增/插入节点方法:
offer(E e) :添加元素系列方法
本质是调LinkedList.add方法,因LinkedList不限制容量大小
,LinkedList.offer方法和add方法无差别
// 默认从队尾巴添加元素
public boolean offer(E e) {
return add(e);
}
//添加元素成新的队首
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
// 添加元素成新的队尾
public boolean offerLast(E e) {
addLast(e);
return true;
}
add(E e):添加元素系列方法
在尾部添加元素,底层逻辑调用linkLast
// 默认从队尾添加元素
public boolean add(E e) {
linkLast(e); // 核心逻辑在 linkLast 方法中
return true;}
//指定位置添加元素
public void add(int index, E element) {
checkPositionIndex(index);// 检查索引有效性
if (index == size)linkLast(element);// 如果是尾部插入
else linkBefore(element, node(index));
}
//添加元素成新队首
public void addFirst(E e) {
linkFirst(e);
}
//添加元素尾部插入
public void addLast(E e) {
linkLast(e);
}
//添加一个已存在集合的全部元素 从队列尾开始插入
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//添加一个已存在集合的全部元素 从队列指定位置index开始插入
public boolean addAll(int index, Collection<? extends E> c) {}
push(E e):(压栈)本质调用addFirst(e)
public void push(E e) {addFirst(e);}
删除/移除 并返回删除节点方法:
remove():移除并返回头元素方法 系列
// 移除队首节点并返回,移除队首节点操作底层是调用unlinkFirst
// 如果队列为空 返回NoSuchElementException异常
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
// 移除队尾节点并返回,移除队尾节点操作底层是调用unlinkLast
// 如果队列为空 返回NoSuchElementException异常
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
//移除元素 参数为空 默认执行removeFirst方法
public E remove() {
return removeFirst();
}
//1.移除指定位置的元素,检查索引是否合法(在0到size-1范围内),有问题则抛出IndexOutOfBoundsException
//2.通过node(index)方法获取指定索引处的节点
//3.调用unlink(Node<E> x)方法移除该节点
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
//调用remove(Object o)方法,所以和remove(Object o)作用一样
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
//移除链表中第一次出现的指定元素(如果存在)
public boolean remove(Object o) {
if (o == null) {// 处理 null 值的移除
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {// 处理非 null 值的移除
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
移除链表中最后出现的指定元素(如果存在) (从队尾开始遍历)
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
poll()移除并返回头元素方法 系列
// 移除队首节点并返回,移除队首节点操作底层是调用unlinkFirst
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
// 移除队首节点并返回,移除队首节点操作底层是调用unlinkFirst
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
移除队尾节点并返回,移除队尾节点操作底层是调用unlinkLast
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
peek():返回元素节点系列方法 队列为空返回null
//获取队首节点 队列为空返回null
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//获取队首节点 队列为空返回null
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//获取队尾节点 队列为空返回null
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
pop():(弹栈)直接调用removeFirst方法 作用一样
public E pop() {
return removeFirst();
}
查找返回节点(不删除)方法:
getFirst():返回队首节点,队列为空 抛出 NoSuchElementException
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;}
getLast():返回队尾节点,队列为空 抛出 NoSuchElementException
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;}
element():直接调用getFirst方法 作用一样
public E element() {return getFirst();}
定位节点索引位置:
indexOf():查找元素第一次出现位置 (从头为0开始)
public int indexOf(Object o) {
int index = 0; // 1. 初始化索引为0
if (o == null) {
// 2. 处理 null 值的查找
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index; // 找到匹配项
index++;
}
} else {
// 3. 处理非 null 值的查找
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index; // 找到匹配项
index++;
}
}
return -1; // 4. 未找到返回-1
}
lastIndexOf(Object o):查找元素最后一次出现位置(从尾开始)
public int lastIndexOf(Object o) {
int index = size; // 1. 初始化索引为链表大小
if (o == null) {
// 2. 处理 null 值的查找
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
// 3. 处理非 null 值的查找
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1; // 4. 未找到返回 -1
}
应用场景
LinkedList实现的接口有三个,所以应用的场景也有三个
作为 List 使用(列表)
// 创建 LinkedList
List<String> list = new LinkedList<>();
// 添加元素
list.add("A"); // 尾部添加
list.add(1, "B"); // 指定位置插入
// 访问元素
String first = list.get(0); // 获取第一个元素
String last = list.get(list.size() - 1); // 获取最后一个元素
// 遍历元素
for (String item : list) {
System.out.println(item);
}
// 删除元素
list.remove(0); // 删除第一个元素
list.remove("B"); // 删除指定元素
作为 Queue 使用 (基本队列FIFO)
public static void main(String[] args) {
//首先对于Queue而言,操作结果有两种类型的返回结果
// 1.报错抛出异常:add remove element 等方法
// 2.返回特殊值:null 或者 false(推荐使用):offer poll peek等方法
//Queue中的阻塞队列ArrayBlockingQueue 可以设置队列容量,超过值添加会报错
Queue<String> blockQueue = new ArrayBlockingQueue<>(2);
// 对于有容量限制的队列添加元素到边界值 会返回.返回特殊值
System.out.println(blockQueue.offer("A")); //添加成功 返回true
System.out.println(blockQueue.offer("A")); //添加成功 返回true
System.out.println(blockQueue.offer("A")); //添加失败 返回false
System.out.println(blockQueue.add("A")); //添加失败 抛出已满的异常IllegalStateException: Queue full
//如果用LinkedList作为队列的子类,是没有设置容量大小 默认就是可无限大小
Queue<String> queue = new LinkedList<>();
System.out.println(queue.poll()); //推荐(返回null)
System.out.println(queue.remove("A")); //false
// System.out.println(queue.remove()); //抛出没有元素异常NoSuchElementException
//因为LinkedList没有容量限制 所以添加多少元素都可以
System.out.println(queue.offer("A")); //true
System.out.println(queue.offer("A")); //true
// 其次验证了LinkedList链表是不关心节点元素是什么 可重复元素节点
System.out.println(queue.add("B")); //true
System.out.println(queue.add("B")); //true
}
作为 Deque 使用 (双端队列)
单从数据节点结构看LinkedList,有Node prev 和next属于双向链表的结构,可以同时定位队列头和尾巴,对于队头和队尾的移除添加操作都是O(1)
对于基本队列(单向链表) 想要操作队尾就需要从队首一路遍历到队尾,所以两种的应用上
1.如果需要频繁执行队首和队尾的场景 选用双端队列
2.如果只需要关注队首的场景,使用基本队列,比较维护prev指针和tail节点需要额外开销
Deque<String> deque = new LinkedList<>();
// 从队列前端操作:添加节点成为新的队首
deque.addFirst("Front");
deque.offerFirst("NewFront");
String front = deque.removeFirst();
// 从队列后端操作节点:队尾添加节点
deque.addLast("End");
deque.offerLast("NewEnd");
String end = deque.removeLast();
// 栈操作 (LIFO)
deque.push("StackTop"); // 压栈方法:push 底层调用addFirst()
String top = deque.pop(); // 弹栈方法:pop 底层调用removeFirst()
使用场景推荐
频繁在头尾操作数据
- 实现队列/双端队列
- 浏览器历史记录管理
- 撤销操作栈(作为栈使用)
不需要频繁随机访问
- 按顺序处理数据流
- 实现消息队列系统
动态数据集合
- 元素数量变化频繁
- 无法预估最大容量
与 ArrayList 对比
特性 | LinkedList | ArrayList |
---|---|---|
底层结构 | 双向链表 | 动态数组 |
随机访问 | 慢 (O(n)) | 快 (O(1)) |
头尾插入/删除 | 快 (O(1)) | 头慢 (O(n)) 尾快 (O(1)) |
中间插入/删除 | 慢 (O(n)) | 慢 (O(n)) |
内存占用 | 较高 (每个节点额外存储两个指针) | 较低 |
内存连续性 | 非连续 | 连续 |
LinkedList 线程不安全
LinkedList 线程不安全体现
情况1:多个线程同时修改链表结构被破坏
当多个线程同时修改链表结构时,会导致链表指针混乱:
LinkedList<Integer> list = new LinkedList<>();
ExecutorService executor = Executors.newFixedThreadPool(2);
// 线程1:添加元素
executor.submit(() -> {
for (int i = 0; i < 1000; i++) {
list.add(i);
}
});
// 线程2:移除元素
executor.submit(() -> {
for (int i = 0; i < 1000; i++) {
if (!list.isEmpty()) {
list.removeFirst();
}
}
});
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.println("最终大小: " + list.size());
// 可能输出:最终大小: 325 (预期为0)
情况2:丢失更新(Add 操作冲突)
当多个线程同时添加元素时:
// 两个线程同时添加元素
executor.submit(() -> {
for (int i = 0; i < 1000; i++) {
list.add(i);
}
});
executor.submit(() -> {
for (int i = 0; i < 1000; i++) {
list.add(i * 100);
}
});
// 预期大小: 2000
// 实际大小: 可能小于2000(如1892)
情况3:快速失败机制ConcurrentModificationException
当遍历链表时另一个线程修改结构:
executor.submit(() -> {
try {
for (Integer num : list) { // 触发迭代器
Thread.sleep(10); // 模拟处理
}
} catch (ConcurrentModificationException e) {
System.out.println("并发修改异常!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
executor.submit(() -> {
for (int i = 0; i < 100; i++) {
list.add(i); // 修改结构
}
});
情况4:死循环和内存泄漏
遍历时进入死循环,节点无法被GC回收最终JVM内存溢出报错
情况5:脏读
一个线程修改时,另一个线程读取到中间状态:
// 线程1添加元素
list.add("A");
list.add("B");
// 线程2在添加过程中读取
// 可能看到: [A] 而不是 [A, B]
解决方案
方案一:使用同步包装器(最简方案)
List<String> syncList = Collections.synchronizedList(new LinkedList<>());
// 写入操作自动同步
syncList.add("item");
// 遍历时需要手动同步
synchronized (syncList) {
for (String item : syncList) {
// 处理元素
}
}
方案二:使用并发集合(最佳方案)
ConcurrentLinkedDeque (非阻塞)
1.无锁算法(CAS实现)2.高并发性能 3.弱一致性迭代器 4.无大小限制
// 不设置队列容量大小,无限制
Deque<String> concurrentDeque = new ConcurrentLinkedDeque<>();
// 并发安全操作
concurrentDeque.addFirst("head");
concurrentDeque.addLast("tail");
String item = concurrentDeque.pollFirst();
// 弱一致性迭代器 操作方法一旦执行必定执行,但结果可能返回也可能不返回
LinkedBlockingDeque (阻塞)
1.有界/无界可选 2.阻塞操作 3.锁分离(put锁和take锁)4.支持超时操作
// 界限可以选择队列给定初始容量大小
BlockingDeque<String> blockingDeque = new LinkedBlockingDeque<>(100);
// 生产者
new Thread(() -> {
while (true) {
blockingDeque.put("data"); // 阻塞如果队列满
}
}).start();
// 消费者
new Thread(() -> {
while (true) {
String data = blockingDeque.take(); // 阻塞如果队列空
// 处理数据
}
}).start();
//生产消费模式互为条件
//阻塞时候 等待合适执行时机再执行操作
方案三: 使用显式锁(更细粒度控制)
优点:灵活控制锁范围 缺点:需手动管理锁
LinkedList<String> list = new LinkedList<>();
ReentrantLock lock = new ReentrantLock();
// 添加元素
lock.lock();
try {
list.add("data");
} finally {
lock.unlock();
}
// 批量操作
lock.lock();
try {
for (int i = 0; i < 100; i++) {
list.add("item-" + i);
}
} finally {
lock.unlock();
}
方案四:使用CopyOnWriteArrayList(特定场景)
List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
// 安全遍历(迭代器使用快照)
for (String item : copyOnWriteList) {
// 即使有修改也不会抛异常
}
// 添加元素(复制整个数组)
copyOnWriteList.add("new item");
适用场景:
1.读多写少(读操作比写操作多100倍以上)
2.遍历操作频繁
3.数据量不大
不适用场景:
1.频繁修改
2.大数据量
3.需要双端队列功能
注意事项
1.避免使用索引遍历:
// 错误方式 - O(n²) 时间复杂度
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 正确方式 - 使用迭代器 (O(n))
for (Iterator<String> it = list.iterator(); it.hasNext();) {
System.out.println(it.next());
}
2.线程安全方案:
List<String> syncList = Collections.synchronizedList(new LinkedList<>());
3.空值处理:
- LinkedList 允许存储
null
元素 - 但队列操作时需注意
poll()
可能返回null
总结
LinkedList
是 Java 集合框架中最灵活的数据结构之一,特别适合需要频繁在集合两端进行操作的场景。它的双向链表结构提供了高效的头部/尾部操作,但随机访问性能较差。根据实际需求选择合适的接口(List/Queue/Deque)进行操作,可以充分发挥其优势。在需要快速随机访问的场景下,应优先考虑 ArrayList
。