云计算百科
云计算领域专业知识百科平台

面试年年考、项目天天用,你真的搞懂 ArrayList 和 LinkedList 的区别了吗?

㊗️本期内容已收录至专栏《Java核心实操(进阶版)》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~ ㊙️本期难度指数:⭐⭐⭐ 🉐福利: 一次订阅后,专栏内的所有文章可永久免费看,持续更新中,保底1000+(篇)硬核实战内容。

前言:从一次线上内存告警说起

那是我接手一个老项目的第三周,凌晨两点多,手机突然震动——监控告警,服务内存飙高,GC 频率异常。

我登上服务器,dump 了一份堆快照,用 MAT 分析了一下,发现一个超大对象链条:一个 LinkedList,里面塞了将近百万条日志记录,每条记录是一个对象,整个链表把 JVM 堆硬生生顶了个对穿。

前任开发的注释写着:「用 LinkedList 是因为要频繁插入,ArrayList 插入慢。」

我盯着这行注释看了好一会儿,心里只有一个想法:他说的不完全是错的,但他对"频繁插入"的理解,可能和实际情况差了十万八千里。

这篇文章,就是从那次告警开始,我重新系统梳理 ArrayList 和 LinkedList 的一次总结。不是为了应付面试,而是因为这两个东西真的天天用、年年考,但真正吃透的人,远比你想象的少。

适合的读者:Java 有一定基础,用过这两个集合,但偶尔还是会在选型时犹豫的同学。

一、先搞清楚它们是什么:回到数据结构的本质

在进入 Java 源码之前,我们先把底层逻辑捋一遍,别嫌烦,这一步是后面所有讨论的根基。

1.1 数组:内存里的一排连号座位

你可以把数组想象成电影院里的一排连号座位。你买票进场,座位号是 A1、A2、A3……一字排开,相邻的座位紧挨着。

这带来一个好处:随机访问极快——我只要知道第一个座位的位置,加上偏移量,就能直接算出第 N 个座位在哪儿,O(1) 时间。

但缺点也明显:中间插入/删除很麻烦——假如要在 A3 和 A4 之间加一个座位,后面所有人都要往后挪一位。座位数量固定,满了就要换个更大的场馆,然后把所有人搬过去。

这就是 ArrayList 的底层逻辑。

1.2 链表:用绳子串起来的一串珠子

链表不一样,每颗珠子除了自身,还有一根绳子指向下一颗珠子(双向链表还有一根指向上一颗)。珠子们可以散落在内存的各个角落,靠绳子串起来。

这带来的好处是:插入和删除理论上很快——只需要改几根绳子就行,O(1),不需要移动其他元素。

但缺点是:不支持真正的随机访问——想找第 N 颗珠子,你得从头开始一颗一颗数,O(N) 时间。

这就是 LinkedList 的底层逻辑。

用一张图来直观对比两者的内存布局:

#mermaid-svg-4DMRCB0D9U2bXrzQ{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4DMRCB0D9U2bXrzQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4DMRCB0D9U2bXrzQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4DMRCB0D9U2bXrzQ .error-icon{fill:#552222;}#mermaid-svg-4DMRCB0D9U2bXrzQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4DMRCB0D9U2bXrzQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4DMRCB0D9U2bXrzQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4DMRCB0D9U2bXrzQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4DMRCB0D9U2bXrzQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4DMRCB0D9U2bXrzQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4DMRCB0D9U2bXrzQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4DMRCB0D9U2bXrzQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4DMRCB0D9U2bXrzQ .marker.cross{stroke:#333333;}#mermaid-svg-4DMRCB0D9U2bXrzQ svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4DMRCB0D9U2bXrzQ p{margin:0;}#mermaid-svg-4DMRCB0D9U2bXrzQ .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-4DMRCB0D9U2bXrzQ .cluster-label text{fill:#333;}#mermaid-svg-4DMRCB0D9U2bXrzQ .cluster-label span{color:#333;}#mermaid-svg-4DMRCB0D9U2bXrzQ .cluster-label span p{background-color:transparent;}#mermaid-svg-4DMRCB0D9U2bXrzQ .label text,#mermaid-svg-4DMRCB0D9U2bXrzQ span{fill:#333;color:#333;}#mermaid-svg-4DMRCB0D9U2bXrzQ .node rect,#mermaid-svg-4DMRCB0D9U2bXrzQ .node circle,#mermaid-svg-4DMRCB0D9U2bXrzQ .node ellipse,#mermaid-svg-4DMRCB0D9U2bXrzQ .node polygon,#mermaid-svg-4DMRCB0D9U2bXrzQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4DMRCB0D9U2bXrzQ .rough-node .label text,#mermaid-svg-4DMRCB0D9U2bXrzQ .node .label text,#mermaid-svg-4DMRCB0D9U2bXrzQ .image-shape .label,#mermaid-svg-4DMRCB0D9U2bXrzQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-4DMRCB0D9U2bXrzQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4DMRCB0D9U2bXrzQ .rough-node .label,#mermaid-svg-4DMRCB0D9U2bXrzQ .node .label,#mermaid-svg-4DMRCB0D9U2bXrzQ .image-shape .label,#mermaid-svg-4DMRCB0D9U2bXrzQ .icon-shape .label{text-align:center;}#mermaid-svg-4DMRCB0D9U2bXrzQ .node.clickable{cursor:pointer;}#mermaid-svg-4DMRCB0D9U2bXrzQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4DMRCB0D9U2bXrzQ .arrowheadPath{fill:#333333;}#mermaid-svg-4DMRCB0D9U2bXrzQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4DMRCB0D9U2bXrzQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4DMRCB0D9U2bXrzQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4DMRCB0D9U2bXrzQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4DMRCB0D9U2bXrzQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4DMRCB0D9U2bXrzQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4DMRCB0D9U2bXrzQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4DMRCB0D9U2bXrzQ .cluster text{fill:#333;}#mermaid-svg-4DMRCB0D9U2bXrzQ .cluster span{color:#333;}#mermaid-svg-4DMRCB0D9U2bXrzQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4DMRCB0D9U2bXrzQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4DMRCB0D9U2bXrzQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-4DMRCB0D9U2bXrzQ .icon-shape,#mermaid-svg-4DMRCB0D9U2bXrzQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4DMRCB0D9U2bXrzQ .icon-shape p,#mermaid-svg-4DMRCB0D9U2bXrzQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4DMRCB0D9U2bXrzQ .icon-shape .label rect,#mermaid-svg-4DMRCB0D9U2bXrzQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4DMRCB0D9U2bXrzQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4DMRCB0D9U2bXrzQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4DMRCB0D9U2bXrzQ :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

LinkedList (分散内存)

next

next

next

next

prev

prev

prev

Nodeprev←'A'→next

Nodeprev←'B'→next

Nodeprev←'C'→next

Nodeprev←'D'→next

null

ArrayList (连续内存)

[0] 'A'

[1] 'B'

[2] 'C'

[3] 'D'

[4] null

好,这两张图印在脑子里,后面我们聊代码的时候就不会迷失了。

二、ArrayList 深度解剖:别小看这个"动态数组"

2.1 底层结构与核心字段

ArrayList 的源码(JDK 17)核心就这几行:

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;

// 真正存数据的数组
transient Object[] elementData;

// 当前元素数量(不是数组容量)
private int size;
}

注意到 elementData 是 transient 的吗?这是个小细节:Java 序列化的时候不会直接序列化这个数组(因为数组可能有空位),而是通过自定义的 writeObject 方法只序列化实际有效的元素。这在频繁序列化的场景下能省不少空间。

还有一个关键接口:RandomAccess。这是个标记接口,没有任何方法,纯粹是给 JDK 内部的工具类(比如 Collections.binarySearch、Collections.sort)用来判断"这个 List 支不支持快速随机访问"的标志。

2.2 扩容机制:那个让人又爱又恨的 1.5 倍

ArrayList 初始容量默认是 10,当元素装满了,会触发扩容。扩容的逻辑在 grow() 方法里:

private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity – oldCapacity, /* 最小增量 */
oldCapacity >> 1 /* 期望增量:原容量的 0.5 倍 */
);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}

oldCapacity >> 1 就是右移一位,等于 oldCapacity / 2,所以新容量 ≈ 原容量的 1.5 倍。

扩容的代价是 Arrays.copyOf,它底层调用的是 System.arraycopy,是一次完整的内存复制。数据量小的时候无所谓,但如果你的列表会长期持有几十万条数据,这个复制操作就值得关注了。

扩容时机流程图:

#mermaid-svg-K49jas9imGiZDhMY{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-K49jas9imGiZDhMY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-K49jas9imGiZDhMY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-K49jas9imGiZDhMY .error-icon{fill:#552222;}#mermaid-svg-K49jas9imGiZDhMY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K49jas9imGiZDhMY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-K49jas9imGiZDhMY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K49jas9imGiZDhMY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K49jas9imGiZDhMY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-K49jas9imGiZDhMY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K49jas9imGiZDhMY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K49jas9imGiZDhMY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K49jas9imGiZDhMY .marker.cross{stroke:#333333;}#mermaid-svg-K49jas9imGiZDhMY svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K49jas9imGiZDhMY p{margin:0;}#mermaid-svg-K49jas9imGiZDhMY .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-K49jas9imGiZDhMY .cluster-label text{fill:#333;}#mermaid-svg-K49jas9imGiZDhMY .cluster-label span{color:#333;}#mermaid-svg-K49jas9imGiZDhMY .cluster-label span p{background-color:transparent;}#mermaid-svg-K49jas9imGiZDhMY .label text,#mermaid-svg-K49jas9imGiZDhMY span{fill:#333;color:#333;}#mermaid-svg-K49jas9imGiZDhMY .node rect,#mermaid-svg-K49jas9imGiZDhMY .node circle,#mermaid-svg-K49jas9imGiZDhMY .node ellipse,#mermaid-svg-K49jas9imGiZDhMY .node polygon,#mermaid-svg-K49jas9imGiZDhMY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-K49jas9imGiZDhMY .rough-node .label text,#mermaid-svg-K49jas9imGiZDhMY .node .label text,#mermaid-svg-K49jas9imGiZDhMY .image-shape .label,#mermaid-svg-K49jas9imGiZDhMY .icon-shape .label{text-anchor:middle;}#mermaid-svg-K49jas9imGiZDhMY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-K49jas9imGiZDhMY .rough-node .label,#mermaid-svg-K49jas9imGiZDhMY .node .label,#mermaid-svg-K49jas9imGiZDhMY .image-shape .label,#mermaid-svg-K49jas9imGiZDhMY .icon-shape .label{text-align:center;}#mermaid-svg-K49jas9imGiZDhMY .node.clickable{cursor:pointer;}#mermaid-svg-K49jas9imGiZDhMY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-K49jas9imGiZDhMY .arrowheadPath{fill:#333333;}#mermaid-svg-K49jas9imGiZDhMY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-K49jas9imGiZDhMY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-K49jas9imGiZDhMY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K49jas9imGiZDhMY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-K49jas9imGiZDhMY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K49jas9imGiZDhMY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-K49jas9imGiZDhMY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-K49jas9imGiZDhMY .cluster text{fill:#333;}#mermaid-svg-K49jas9imGiZDhMY .cluster span{color:#333;}#mermaid-svg-K49jas9imGiZDhMY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-K49jas9imGiZDhMY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-K49jas9imGiZDhMY rect.text{fill:none;stroke-width:0;}#mermaid-svg-K49jas9imGiZDhMY .icon-shape,#mermaid-svg-K49jas9imGiZDhMY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K49jas9imGiZDhMY .icon-shape p,#mermaid-svg-K49jas9imGiZDhMY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-K49jas9imGiZDhMY .icon-shape .label rect,#mermaid-svg-K49jas9imGiZDhMY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K49jas9imGiZDhMY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-K49jas9imGiZDhMY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-K49jas9imGiZDhMY :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

是

否

调用 add(e)

size < elementData.length ?

直接赋值elementData[size++] = e

触发 grow()

计算新容量newCap ≈ oldCap × 1.5

Arrays.copyOf创建新数组并复制

更新 elementData 引用

add 完成

一个实际中很容易踩的坑:

// 坏味道:如果你知道大概要存多少元素,千万不要这样
List<String> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
list.add("item" + i);
}

// 好做法:预估容量,避免多次扩容
List<String> list = new ArrayList<>(100000);
for (int i = 0; i < 100000; i++) {
list.add("item" + i);
}

这不是在抠细节——在高并发或者大数据量的场景下,频繁扩容产生的临时对象会给 GC 带来不小的压力。

2.3 随机访问为什么这么快?

public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index); // 直接下标访问
}

E elementData(int index) {
return (E) elementData[index];
}

就这。数组下标访问,O(1),CPU 缓存命中率极高,因为内存是连续的,预取效率也好。这是 ArrayList 最大的优势,也是它适合"读多写少"场景的根本原因。

三、LinkedList 深度解剖:被误解最深的集合之一

3.1 节点结构:双向链表的实现

LinkedList 底层是一个标准的双向链表,核心是内部类 Node<E>:

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 本身维护了三个字段:

transient int size = 0;
transient Node<E> first; // 头节点
transient Node<E> last; // 尾节点

有了 first 和 last,在头部和尾部的操作(addFirst、addLast、removeFirst、removeLast)都是真正的 O(1)。

3.2 LinkedList 的另一面:它实现了 Deque

这一点很多人忽略了。LinkedList 不只实现了 List,还实现了 Deque(双端队列)接口:

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable

这意味着它可以当栈用,也可以当队列用:

// 当队列使用
Deque<String> queue = new LinkedList<>();
queue.offer("first"); // 入队(尾部添加)
queue.offer("second");
String head = queue.poll(); // 出队(头部移除)

// 当栈使用
Deque<String> stack = new LinkedList<>();
stack.push("bottom");
stack.push("top");
String top = stack.pop(); // 出栈(头部移除)

注意:如果你只需要一个栈,官方更推荐用 ArrayDeque,而不是 LinkedList。原因后面会讲。

3.3 那个"O(1) 插入"到底在什么情况下成立?

这是最核心的误区,也是我前言里那个前任开发踩坑的根源。

LinkedList 在已知节点引用的情况下,插入和删除是 O(1)。但问题在于:你怎么拿到那个节点引用?

如果你是通过索引来操作:

list.add(50000, "someValue"); // 在第 50000 个位置插入

LinkedList 内部会先做一次 node(index) 遍历:

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;
}
}

即使有这个"从近端开始遍历"的小优化,最坏情况依然是 O(N/2),本质还是线性时间。所以那个"LinkedList 插入快"的说法,是在混淆"拿到节点后的插入操作"和"按索引插入这个完整操作"。

这就是前任开发的坑所在:他面对的是"按位置插入"的场景,LinkedList 在这种情况下并不比 ArrayList 快多少,但内存开销却大了好几倍。

四、核心对比:用数字和逻辑说话

4.1 时间复杂度对比

操作ArrayListLinkedList
get(index) O(1) O(N)
add(e)(尾部追加) 均摊 O(1) O(1)
add(index, e)(中间插入) O(N) O(N)*
remove(index) O(N) O(N)*
addFirst / addLast O(N) / O(1) O(1)
removeFirst / removeLast O(N) / O(1) O(1)
contains(o) O(N) O(N)
iterator 顺序遍历 O(N),缓存友好 O(N),缓存不友好

*:LinkedList 中间插入/删除,需要先 O(N) 遍历找到节点,然后 O(1) 修改指针。

4.2 内存占用对比

这个比较往往被忽视,但在大数据量场景下非常关键。

ArrayList:

  • 每个元素:一个对象引用,64 位 JVM 下通常是 4~8 字节(取决于是否开启指针压缩)
  • 有额外的空余容量(capacity > size 时存在空位)
  • 无额外指针开销

LinkedList:

  • 每个元素:一个 Node 对象 = item 引用 + prev 引用 + next 引用 + 对象头
  • 64 位 JVM 下,一个空的 Node 对象至少 32~40 字节
  • 100 万个元素 → 额外消耗约 30~40 MB 纯指针开销

这就是当年那次告警的根本原因——百万级别的 LinkedList 节点,每个节点都是独立的堆对象,GC 压力指数级上升。

#mermaid-svg-O8nUYi14g5sye5JF{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-O8nUYi14g5sye5JF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-O8nUYi14g5sye5JF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-O8nUYi14g5sye5JF .error-icon{fill:#552222;}#mermaid-svg-O8nUYi14g5sye5JF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-O8nUYi14g5sye5JF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-O8nUYi14g5sye5JF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-O8nUYi14g5sye5JF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-O8nUYi14g5sye5JF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-O8nUYi14g5sye5JF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-O8nUYi14g5sye5JF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-O8nUYi14g5sye5JF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-O8nUYi14g5sye5JF .marker.cross{stroke:#333333;}#mermaid-svg-O8nUYi14g5sye5JF svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-O8nUYi14g5sye5JF p{margin:0;}#mermaid-svg-O8nUYi14g5sye5JF .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-O8nUYi14g5sye5JF .cluster-label text{fill:#333;}#mermaid-svg-O8nUYi14g5sye5JF .cluster-label span{color:#333;}#mermaid-svg-O8nUYi14g5sye5JF .cluster-label span p{background-color:transparent;}#mermaid-svg-O8nUYi14g5sye5JF .label text,#mermaid-svg-O8nUYi14g5sye5JF span{fill:#333;color:#333;}#mermaid-svg-O8nUYi14g5sye5JF .node rect,#mermaid-svg-O8nUYi14g5sye5JF .node circle,#mermaid-svg-O8nUYi14g5sye5JF .node ellipse,#mermaid-svg-O8nUYi14g5sye5JF .node polygon,#mermaid-svg-O8nUYi14g5sye5JF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-O8nUYi14g5sye5JF .rough-node .label text,#mermaid-svg-O8nUYi14g5sye5JF .node .label text,#mermaid-svg-O8nUYi14g5sye5JF .image-shape .label,#mermaid-svg-O8nUYi14g5sye5JF .icon-shape .label{text-anchor:middle;}#mermaid-svg-O8nUYi14g5sye5JF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-O8nUYi14g5sye5JF .rough-node .label,#mermaid-svg-O8nUYi14g5sye5JF .node .label,#mermaid-svg-O8nUYi14g5sye5JF .image-shape .label,#mermaid-svg-O8nUYi14g5sye5JF .icon-shape .label{text-align:center;}#mermaid-svg-O8nUYi14g5sye5JF .node.clickable{cursor:pointer;}#mermaid-svg-O8nUYi14g5sye5JF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-O8nUYi14g5sye5JF .arrowheadPath{fill:#333333;}#mermaid-svg-O8nUYi14g5sye5JF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-O8nUYi14g5sye5JF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-O8nUYi14g5sye5JF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O8nUYi14g5sye5JF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-O8nUYi14g5sye5JF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O8nUYi14g5sye5JF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-O8nUYi14g5sye5JF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-O8nUYi14g5sye5JF .cluster text{fill:#333;}#mermaid-svg-O8nUYi14g5sye5JF .cluster span{color:#333;}#mermaid-svg-O8nUYi14g5sye5JF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-O8nUYi14g5sye5JF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-O8nUYi14g5sye5JF rect.text{fill:none;stroke-width:0;}#mermaid-svg-O8nUYi14g5sye5JF .icon-shape,#mermaid-svg-O8nUYi14g5sye5JF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O8nUYi14g5sye5JF .icon-shape p,#mermaid-svg-O8nUYi14g5sye5JF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-O8nUYi14g5sye5JF .icon-shape .label rect,#mermaid-svg-O8nUYi14g5sye5JF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O8nUYi14g5sye5JF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-O8nUYi14g5sye5JF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-O8nUYi14g5sye5JF :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

100万元素内存对比

ArrayList元素本身:~4MB空余容量:~0.8MB总计:≈ 5MB

LinkedList元素本身:~4MBNode对象开销:≈ 38MB总计:≈ 42MB

(以上数字基于 64位 JVM + 对象引用 4 字节 + Node 对象 38 字节的估算,实际因 JVM 参数不同会有差异)

4.3 迭代性能:CPU 缓存是个大变量

理论上两者都是 O(N) 遍历,但实际性能差距可能大到让你吃惊。

原因在于 CPU 缓存局部性(Cache Locality):

  • ArrayList 底层是连续数组,遍历时 CPU 预取(prefetch)机制能提前把后续数据加载进 L1/L2 缓存,实际访问时命中率极高;
  • LinkedList 每个节点分散在堆的各个角落,遍历时每次跳转到下一个节点都可能触发缓存 miss,需要重新从主内存加载。

这个差距在现代 CPU 上可以达到 5~10 倍甚至更多,这也是为什么即使只是顺序遍历,ArrayList 通常也比 LinkedList 快的原因。

五、实战代码:各种场景下的对比演示

5.1 基准测试:用 JMH 量化差距

我写了一组 JMH 基准测试(精简版,实际生产建议用完整配置):

import org.openjdk.jmh.annotations.*;
import java.util.*;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
public class ListBenchmark {

private static final int SIZE = 100_000;

private List<Integer> arrayList;
private List<Integer> linkedList;

@Setup
public void setup() {
arrayList = new ArrayList<>(SIZE);
linkedList = new LinkedList<>();
for (int i = 0; i < SIZE; i++) {
arrayList.add(i);
linkedList.add(i);
}
}

// === 随机访问 ===
@Benchmark
public int randomAccessArrayList() {
return arrayList.get(SIZE / 2); // 访问中间元素
}

@Benchmark
public int randomAccessLinkedList() {
return linkedList.get(SIZE / 2); // 访问中间元素
}

// === 顺序迭代 ===
@Benchmark
public long iterateArrayList() {
long sum = 0;
for (int val : arrayList) sum += val;
return sum;
}

@Benchmark
public long iterateLinkedList() {
long sum = 0;
for (int val : linkedList) sum += val;
return sum;
}

// === 头部插入 ===
@Benchmark
public void addFirstArrayList() {
arrayList.add(0, 999);
arrayList.remove(0); // 保持 size 稳定
}

@Benchmark
public void addFirstLinkedList() {
((LinkedList<Integer>) linkedList).addFirst(999);
((LinkedList<Integer>) linkedList).removeFirst();
}
}

实测参考结果(10万元素,MacBook Pro M1,仅供参考,实际跑起来会有差异):

测试项ArrayListLinkedList差距
随机访问(中间) ~0.01 µs ~150 µs 约 15000 倍
顺序迭代 ~200 µs ~800 µs 约 4 倍
头部插入 ~50 µs ~0.05 µs LinkedList 快 1000 倍

看到了吗?随机访问差距可以达到四位数倍,但头部插入 LinkedList 确实遥遥领先——前提是你用的是 addFirst 这种接口,而不是 add(0, e)(虽然效果一样,但后者表意更模糊)。

5.2 实战场景一:电商订单列表(读多写少)

这种场景几乎是 ArrayList 的主场。

// 订单服务:查询用户订单列表,分页展示
@Service
public class OrderService {

@Autowired
private OrderMapper orderMapper;

/**
* 查询用户订单,并对结果做二次处理
* 典型的"构建一次、多次读取"场景
*/

public List<OrderVO> getUserOrders(Long userId, int page, int size) {
// 从 DB 取回原始数据(数量可预估),提前设置容量
List<OrderDO> rawOrders = orderMapper.selectByUserId(userId, page, size);

// 预分配 capacity,避免扩容
List<OrderVO> result = new ArrayList<>(rawOrders.size());

for (OrderDO order : rawOrders) {
// 数据转换
OrderVO vo = convertToVO(order);
result.add(vo);
}

// 后续可能还会多次 get(i) 访问,ArrayList 完胜
return result;
}

private OrderVO convertToVO(OrderDO order) {
// … 字段映射
return new OrderVO();
}
}

这里没什么花哨的,但每一行都有用意:

  • new ArrayList<>(rawOrders.size()):用已知 size 初始化,零扩容;
  • 返回 List<OrderVO> 接口类型,不暴露具体实现——未来换实现类也不需要改调用方。

5.3 实战场景二:任务调度队列(频繁头部操作)

这才是 LinkedList(或者说 ArrayDeque)真正适合的场景:

// 简化版任务队列:模拟一个先进先出的任务处理系统
public class TaskQueue {

// 注意:这里用 Deque 接口声明,不暴露具体实现
private final Deque<Runnable> queue = new ArrayDeque<>(); // 实际更推荐 ArrayDeque
// private final Deque<Runnable> queue = new LinkedList<>(); // 也可以,但内存效率略差

public synchronized void enqueue(Runnable task) {
queue.offerLast(task); // 尾部入队
}

public synchronized Runnable dequeue() {
return queue.pollFirst(); // 头部出队,O(1)
}

public boolean isEmpty() {
return queue.isEmpty();
}

// 调度线程
public void startProcessing() {
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
Runnable task = dequeue();
if (task != null) {
try {
task.run();
} catch (Exception e) {
// 记录日志,继续处理下一个
System.err.println("Task failed: " + e.getMessage());
}
} else {
// 队列空了,短暂休眠避免 CPU 空转
try {
Thread.sleep(10);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
});
worker.setDaemon(true);
worker.start();
}
}

等等,我刚才说"实际更推荐 ArrayDeque"——这需要解释一下。

六、插播:为什么很多场景下 ArrayDeque 比 LinkedList 更香?

ArrayDeque 是 Java 6 引入的双端队列实现,底层是循环数组。

它和 LinkedList 作为队列/栈的对比:

特性LinkedListArrayDeque
头尾操作 O(1) O(1)(均摊)
内存效率 每个元素额外 ~32 字节 仅数组引用,无额外开销
缓存友好性 差(节点分散) 好(连续数组)
支持 null 元素 ✅ ❌
List 接口 ✅ ❌

ArrayDeque 几乎在所有队列/栈场景下性能都优于 LinkedList,除非你需要 null 元素或者需要同时当 List 使用(这种需求本身就值得反思设计)。

Java 官方文档也在 ArrayDeque 的 Javadoc 里明确说:

“This class is likely to be faster than Stack when used as a stack, and faster than LinkedList when used as a queue.”

翻译过来就是:别再用 LinkedList 当队列了。

七、选型决策树:一图说清楚

面对具体场景,怎么选?这张图应该能帮你快速决策:

#mermaid-svg-9TVHsufmg1sPpokH{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9TVHsufmg1sPpokH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9TVHsufmg1sPpokH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9TVHsufmg1sPpokH .error-icon{fill:#552222;}#mermaid-svg-9TVHsufmg1sPpokH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9TVHsufmg1sPpokH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9TVHsufmg1sPpokH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9TVHsufmg1sPpokH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9TVHsufmg1sPpokH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9TVHsufmg1sPpokH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9TVHsufmg1sPpokH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9TVHsufmg1sPpokH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9TVHsufmg1sPpokH .marker.cross{stroke:#333333;}#mermaid-svg-9TVHsufmg1sPpokH svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9TVHsufmg1sPpokH p{margin:0;}#mermaid-svg-9TVHsufmg1sPpokH .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-9TVHsufmg1sPpokH .cluster-label text{fill:#333;}#mermaid-svg-9TVHsufmg1sPpokH .cluster-label span{color:#333;}#mermaid-svg-9TVHsufmg1sPpokH .cluster-label span p{background-color:transparent;}#mermaid-svg-9TVHsufmg1sPpokH .label text,#mermaid-svg-9TVHsufmg1sPpokH span{fill:#333;color:#333;}#mermaid-svg-9TVHsufmg1sPpokH .node rect,#mermaid-svg-9TVHsufmg1sPpokH .node circle,#mermaid-svg-9TVHsufmg1sPpokH .node ellipse,#mermaid-svg-9TVHsufmg1sPpokH .node polygon,#mermaid-svg-9TVHsufmg1sPpokH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9TVHsufmg1sPpokH .rough-node .label text,#mermaid-svg-9TVHsufmg1sPpokH .node .label text,#mermaid-svg-9TVHsufmg1sPpokH .image-shape .label,#mermaid-svg-9TVHsufmg1sPpokH .icon-shape .label{text-anchor:middle;}#mermaid-svg-9TVHsufmg1sPpokH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9TVHsufmg1sPpokH .rough-node .label,#mermaid-svg-9TVHsufmg1sPpokH .node .label,#mermaid-svg-9TVHsufmg1sPpokH .image-shape .label,#mermaid-svg-9TVHsufmg1sPpokH .icon-shape .label{text-align:center;}#mermaid-svg-9TVHsufmg1sPpokH .node.clickable{cursor:pointer;}#mermaid-svg-9TVHsufmg1sPpokH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9TVHsufmg1sPpokH .arrowheadPath{fill:#333333;}#mermaid-svg-9TVHsufmg1sPpokH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9TVHsufmg1sPpokH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9TVHsufmg1sPpokH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9TVHsufmg1sPpokH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9TVHsufmg1sPpokH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9TVHsufmg1sPpokH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9TVHsufmg1sPpokH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9TVHsufmg1sPpokH .cluster text{fill:#333;}#mermaid-svg-9TVHsufmg1sPpokH .cluster span{color:#333;}#mermaid-svg-9TVHsufmg1sPpokH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9TVHsufmg1sPpokH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9TVHsufmg1sPpokH rect.text{fill:none;stroke-width:0;}#mermaid-svg-9TVHsufmg1sPpokH .icon-shape,#mermaid-svg-9TVHsufmg1sPpokH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9TVHsufmg1sPpokH .icon-shape p,#mermaid-svg-9TVHsufmg1sPpokH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9TVHsufmg1sPpokH .icon-shape .label rect,#mermaid-svg-9TVHsufmg1sPpokH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9TVHsufmg1sPpokH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9TVHsufmg1sPpokH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9TVHsufmg1sPpokH :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

随机访问 / 遍历

头尾操作为主队列/栈

中间频繁插入删除

是

否

是

否

能(使用 Iterator)

不能(按索引)

我需要一个 List / 队列

主要操作是什么?

数据量大吗?

需要 List 接口吗?

能拿到节点引用吗?

✅ ArrayList预设初始容量

✅ ArrayList默认即可

⚠️ LinkedList但考虑是否真的需要 List

✅ ArrayDeque性能更优

✅ LinkedList此时插删是真正 O(1)

✅ ArrayList两者实际差不多,ArrayList 缓存更友好

八、高级话题:并发场景下的注意事项

8.1 两者都不是线程安全的

这个必须强调:ArrayList 和 LinkedList 都不是线程安全的。多线程并发修改会导致数据不一致,甚至抛出 ConcurrentModificationException。

// 危险代码:多线程并发 add
List<Integer> list = new ArrayList<>();
ExecutorService pool = Executors.newFixedThreadPool(10);

for (int i = 0; i < 1000; i++) {
final int val = i;
pool.submit(() -> list.add(val)); // 不安全!
}

pool.shutdown();
pool.awaitTermination(5, TimeUnit.SECONDS);

// list.size() 可能小于 1000,甚至抛异常
System.out.println(list.size());

8.2 线程安全的替代方案

// 方案一:Collections.synchronizedList(全方法加锁,性能一般)
List<Integer> safeList = Collections.synchronizedList(new ArrayList<>());

// 方案二:CopyOnWriteArrayList(读不加锁,写时复制,适合读多写极少)
List<Integer> cowList = new CopyOnWriteArrayList<>();

// 方案三:LinkedBlockingDeque(如果需要队列语义)
Deque<Integer> blockingDeque = new LinkedBlockingDeque<>();

选哪个取决于读写比例:

  • 读多写极少(比如配置缓存):CopyOnWriteArrayList,读性能接近原生 ArrayList;
  • 读写均衡:Collections.synchronizedList 配合手动同步块;
  • 生产者-消费者:LinkedBlockingQueue 或 ArrayBlockingQueue,语义更清晰。

九、那次告警之后,我做了什么?

回到开头的故事。找到问题之后,修复方案其实很简单:

  • 把 LinkedList 换成 ArrayList:该场景是"积累一批日志然后批量写库",完全是追加操作,没有中间插入,ArrayList 完全满足;

  • 加了容量预估:根据单批次日志量的历史均值,设置了初始容量,消除了扩容开销;

  • 加了批次上限:原来的 LinkedList 没有大小限制,内存就这么无限涨上去了。加了一个 MAX_BATCH_SIZE = 10000 的硬限制,超出就触发一次写库并清空列表。

  • // 修复后的日志批处理器(简化示意)
    public class BatchLogProcessor {

    private static final int MAX_BATCH_SIZE = 10_000;
    private static final int INITIAL_CAPACITY = 1_024;

    // 关键改动 1:换成 ArrayList,并预设合理初始容量
    private List<LogEntry> buffer = new ArrayList<>(INITIAL_CAPACITY);

    public synchronized void append(LogEntry entry) {
    buffer.add(entry);

    // 关键改动 2:加了批次上限,防止内存无限增长
    if (buffer.size() >= MAX_BATCH_SIZE) {
    flush();
    }
    }

    public synchronized void flush() {
    if (buffer.isEmpty()) return;

    List<LogEntry> toFlush = buffer;
    // 关键改动 3:直接替换引用,不在原列表上 clear()
    // clear() 会保留内部数组,替换引用让 GC 回收旧数组
    buffer = new ArrayList<>(INITIAL_CAPACITY);

    // 异步写库
    CompletableFuture.runAsync(() -> logDao.batchInsert(toFlush));
    }
    }

    修复上线后,内存告警消失了,GC 频率也恢复正常。

    这件事给我的最大教训是:"某某操作在链表上理论上是 O(1)“这句话,永远要加上"在什么前提下”。

    十、常见面试问题的深度解析

    既然这也是面试必考题,那就顺手把几个经典问法过一遍,给你一个有深度的回答思路,而不是背八股文。

    Q1:“ArrayList 和 LinkedList 的区别?”

    别只说"一个数组一个链表",可以这样展开:

    底层结构不同带来的核心差异在于访问模式:ArrayList 支持 O(1) 随机访问,适合读多写少;LinkedList 在头尾操作上是真正的 O(1),但按索引的中间插删要先遍历,本质还是 O(N)。内存上 LinkedList 的 Node 对象开销约是 ArrayList 的 5~8 倍。实际项目中,90% 的场景用 ArrayList 就够了,需要队列/双端队列语义时优先考虑 ArrayDeque。

    Q2:“什么情况下 LinkedList 比 ArrayList 快?”

    • 严格头部/尾部的插入删除(addFirst、removeFirst 等),且操作非常频繁;
    • 通过 Iterator 在遍历过程中做插入/删除(iterator.remove() 对链表是真 O(1));
    • 不需要随机访问,数据规模不大(避免缓存 miss 的负面影响)。

    Q3:“为什么不推荐用 LinkedList?”

    这其实是个开放题,答好了能体现你对工程的理解:

    不是"不推荐",而是它适合的场景相当窄:必须是头尾操作为主、不需要随机访问、且内存不是瓶颈。而这些场景用 ArrayDeque 通常更合适,因为缓存友好性更好、内存开销更小。LinkedList 最大的问题是被滥用:很多人听说"链表插入快"就无脑用,结果在按索引操作的场景下性能反而更差,内存还多用好几倍。

    十一、总结:三句话收场

    一:大部分时候,用 ArrayList。 随机访问快、缓存友好、内存紧凑,覆盖了 90% 的使用场景。预估数量时记得设初始容量。

    二:需要队列/栈语义,用 ArrayDeque,不是 LinkedList。 更快、更省内存,除非你真的需要 null 元素或者 List 接口。

    三:选型之前,先想清楚你的主要操作是什么。 不是"链表插入 O(1) 所以我要用链表"——要想清楚这个 O(1) 在你的场景里能不能成立,成立的前提代价是什么。

    最后,真诚地建议你:下次在项目里用 LinkedList 之前,先想一下那个前任开发的注释,再想一下那次凌晨两点的告警。


    本文基于 JDK 17 源码分析,部分性能数据为参考估算,实际结果因 JVM 版本、硬件环境不同而有所差异,建议结合 JMH 在目标环境实测。

    🌟 文末

    哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛

    好啦~以上就是本期 《Java核心实操(进阶版)》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!

    小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~ 三连就是对我写作道路上最好的鼓励与支持! ❤️🔥

    📌 专栏持续更新中|建议收藏 + 订阅

    专栏 👉 《Java核心实操(进阶版)》,我会按照“入门 → 进阶 → 工程化 → 项目落地”的路线持续更新,争取让每一篇都做到:

    ✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)

    📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~


    ⭐️ 若喜欢我,就请关注我叭~(更新不迷路) ⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力) ⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)


    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 面试年年考、项目天天用,你真的搞懂 ArrayList 和 LinkedList 的区别了吗?
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!