ArrayList vs LinkedList:四维对比,该怎么选?

面试最常问的一个集合对比题就是 ArrayList 和 LinkedList 的区别。

大多数候选人的回答:"ArrayList 查询快,LinkedList 增删快。"

这句话对不对?算半对。它漏了一个很重要的前提------增删的位置是否有给定节点。而且很多人在比较两者的的时候,用的时间复杂度分析是错的。

这篇文章从四个维度把这两个货掰开揉碎了对比:底层数据结构、操作效率、内存占用、线程安全。看完你就能答出面试官想听的那个版本。

底层数据结构:数组 vs 双向链表

这是所有差异的根源。

ArrayList 底层是 Object 数组,内存连续。LinkedList 底层是双向链表,每个节点存数据 + next 指针 + prev 指针,内存不连续。
#mermaid-svg-YqJh1TqMdeQdby26{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-YqJh1TqMdeQdby26 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YqJh1TqMdeQdby26 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YqJh1TqMdeQdby26 .error-icon{fill:#552222;}#mermaid-svg-YqJh1TqMdeQdby26 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YqJh1TqMdeQdby26 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YqJh1TqMdeQdby26 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YqJh1TqMdeQdby26 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YqJh1TqMdeQdby26 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YqJh1TqMdeQdby26 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YqJh1TqMdeQdby26 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YqJh1TqMdeQdby26 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YqJh1TqMdeQdby26 .marker.cross{stroke:#333333;}#mermaid-svg-YqJh1TqMdeQdby26 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YqJh1TqMdeQdby26 p{margin:0;}#mermaid-svg-YqJh1TqMdeQdby26 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YqJh1TqMdeQdby26 .cluster-label text{fill:#333;}#mermaid-svg-YqJh1TqMdeQdby26 .cluster-label span{color:#333;}#mermaid-svg-YqJh1TqMdeQdby26 .cluster-label span p{background-color:transparent;}#mermaid-svg-YqJh1TqMdeQdby26 .label text,#mermaid-svg-YqJh1TqMdeQdby26 span{fill:#333;color:#333;}#mermaid-svg-YqJh1TqMdeQdby26 .node rect,#mermaid-svg-YqJh1TqMdeQdby26 .node circle,#mermaid-svg-YqJh1TqMdeQdby26 .node ellipse,#mermaid-svg-YqJh1TqMdeQdby26 .node polygon,#mermaid-svg-YqJh1TqMdeQdby26 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YqJh1TqMdeQdby26 .rough-node .label text,#mermaid-svg-YqJh1TqMdeQdby26 .node .label text,#mermaid-svg-YqJh1TqMdeQdby26 .image-shape .label,#mermaid-svg-YqJh1TqMdeQdby26 .icon-shape .label{text-anchor:middle;}#mermaid-svg-YqJh1TqMdeQdby26 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YqJh1TqMdeQdby26 .rough-node .label,#mermaid-svg-YqJh1TqMdeQdby26 .node .label,#mermaid-svg-YqJh1TqMdeQdby26 .image-shape .label,#mermaid-svg-YqJh1TqMdeQdby26 .icon-shape .label{text-align:center;}#mermaid-svg-YqJh1TqMdeQdby26 .node.clickable{cursor:pointer;}#mermaid-svg-YqJh1TqMdeQdby26 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YqJh1TqMdeQdby26 .arrowheadPath{fill:#333333;}#mermaid-svg-YqJh1TqMdeQdby26 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YqJh1TqMdeQdby26 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YqJh1TqMdeQdby26 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YqJh1TqMdeQdby26 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YqJh1TqMdeQdby26 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YqJh1TqMdeQdby26 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YqJh1TqMdeQdby26 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YqJh1TqMdeQdby26 .cluster text{fill:#333;}#mermaid-svg-YqJh1TqMdeQdby26 .cluster span{color:#333;}#mermaid-svg-YqJh1TqMdeQdby26 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-YqJh1TqMdeQdby26 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YqJh1TqMdeQdby26 rect.text{fill:none;stroke-width:0;}#mermaid-svg-YqJh1TqMdeQdby26 .icon-shape,#mermaid-svg-YqJh1TqMdeQdby26 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YqJh1TqMdeQdby26 .icon-shape p,#mermaid-svg-YqJh1TqMdeQdby26 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YqJh1TqMdeQdby26 .icon-shape .label rect,#mermaid-svg-YqJh1TqMdeQdby26 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YqJh1TqMdeQdby26 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YqJh1TqMdeQdby26 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YqJh1TqMdeQdby26 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} LinkedList(双向链表,非连续内存)
Node A

prev: null
Node B

prev: ←
Node C

prev: ←
Node D

next: null
ArrayList(数组,连续内存)

0

1

2

3

...

LinkedList 的 JDK 1.8 源码里,Node 节点长这样:

java 复制代码
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 两个指针,分别指向头节点和尾节点。所以访问头和尾都是 O(1),不需要遍历。

操作效率:逐项拆解

查询操作

操作 ArrayList LinkedList
按下标查询 O(1) 不支持,需遍历
查头结点 O(1) O(1)
查尾结点 O(1) O(1)
查找指定元素 O(n) O(n)

ArrayList 按索引查询是 O(1),因为内存连续,直接用寻址公式 baseAddress + i × dataSize 就能算出来。

LinkedList 没有下标概念,你传一个 get(3),它内部是从 first 开始一个节点一个节点往后数。源码里还有一个优化------它会判断你要找的索引离头近还是离尾近,从近的一端开始遍历:

java 复制代码
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),只是平均少走一半路程。

对于"查找指定元素"(不知道索引),两者都要遍历,都是 O(n)。

增删操作------这是重点

很多人说"LinkedList 增删快",其实要分情况。
#mermaid-svg-n4VzPf4kvvPxyAWJ{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-n4VzPf4kvvPxyAWJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-n4VzPf4kvvPxyAWJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-n4VzPf4kvvPxyAWJ .error-icon{fill:#552222;}#mermaid-svg-n4VzPf4kvvPxyAWJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-n4VzPf4kvvPxyAWJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-n4VzPf4kvvPxyAWJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-n4VzPf4kvvPxyAWJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-n4VzPf4kvvPxyAWJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-n4VzPf4kvvPxyAWJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-n4VzPf4kvvPxyAWJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-n4VzPf4kvvPxyAWJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-n4VzPf4kvvPxyAWJ .marker.cross{stroke:#333333;}#mermaid-svg-n4VzPf4kvvPxyAWJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-n4VzPf4kvvPxyAWJ p{margin:0;}#mermaid-svg-n4VzPf4kvvPxyAWJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-n4VzPf4kvvPxyAWJ .cluster-label text{fill:#333;}#mermaid-svg-n4VzPf4kvvPxyAWJ .cluster-label span{color:#333;}#mermaid-svg-n4VzPf4kvvPxyAWJ .cluster-label span p{background-color:transparent;}#mermaid-svg-n4VzPf4kvvPxyAWJ .label text,#mermaid-svg-n4VzPf4kvvPxyAWJ span{fill:#333;color:#333;}#mermaid-svg-n4VzPf4kvvPxyAWJ .node rect,#mermaid-svg-n4VzPf4kvvPxyAWJ .node circle,#mermaid-svg-n4VzPf4kvvPxyAWJ .node ellipse,#mermaid-svg-n4VzPf4kvvPxyAWJ .node polygon,#mermaid-svg-n4VzPf4kvvPxyAWJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-n4VzPf4kvvPxyAWJ .rough-node .label text,#mermaid-svg-n4VzPf4kvvPxyAWJ .node .label text,#mermaid-svg-n4VzPf4kvvPxyAWJ .image-shape .label,#mermaid-svg-n4VzPf4kvvPxyAWJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-n4VzPf4kvvPxyAWJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-n4VzPf4kvvPxyAWJ .rough-node .label,#mermaid-svg-n4VzPf4kvvPxyAWJ .node .label,#mermaid-svg-n4VzPf4kvvPxyAWJ .image-shape .label,#mermaid-svg-n4VzPf4kvvPxyAWJ .icon-shape .label{text-align:center;}#mermaid-svg-n4VzPf4kvvPxyAWJ .node.clickable{cursor:pointer;}#mermaid-svg-n4VzPf4kvvPxyAWJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-n4VzPf4kvvPxyAWJ .arrowheadPath{fill:#333333;}#mermaid-svg-n4VzPf4kvvPxyAWJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-n4VzPf4kvvPxyAWJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-n4VzPf4kvvPxyAWJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-n4VzPf4kvvPxyAWJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-n4VzPf4kvvPxyAWJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-n4VzPf4kvvPxyAWJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-n4VzPf4kvvPxyAWJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-n4VzPf4kvvPxyAWJ .cluster text{fill:#333;}#mermaid-svg-n4VzPf4kvvPxyAWJ .cluster span{color:#333;}#mermaid-svg-n4VzPf4kvvPxyAWJ 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-n4VzPf4kvvPxyAWJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-n4VzPf4kvvPxyAWJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-n4VzPf4kvvPxyAWJ .icon-shape,#mermaid-svg-n4VzPf4kvvPxyAWJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-n4VzPf4kvvPxyAWJ .icon-shape p,#mermaid-svg-n4VzPf4kvvPxyAWJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-n4VzPf4kvvPxyAWJ .icon-shape .label rect,#mermaid-svg-n4VzPf4kvvPxyAWJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-n4VzPf4kvvPxyAWJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-n4VzPf4kvvPxyAWJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-n4VzPf4kvvPxyAWJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 头部
尾部
中间位置
给定节点删除
增删操作
在什么位置?
ArrayList: O(n)

LinkedList: O(1)
ArrayList: O(1)

LinkedList: O(1)
ArrayList: O(n) 挪元素

LinkedList: O(n) 遍历定位

整体差不多
ArrayList: N/A

LinkedList: O(1)

关键分四种情况来看:

1. 尾部添加

两者都很快。ArrayList 直接在 size 位置写数据 O(1),偶尔触发扩容。LinkedList 把 last 的 next 指向新节点,last 指针更新,O(1)。

2. 头部添加

LinkedList 完胜 O(1),直接改 first 指针。ArrayList 需要把现有元素全部往后挪一位,O(n)。

3. 中间位置插入

两者差别不大。LinkedList 虽然插入动作本身是 O(1),但要先遍历到那个位置,遍历是 O(n)。ArrayList 需要移动元素也是 O(n)。所以实际测试中,大量中间位置插入,ArrayList 反而可能更快------因为数组内存连续,CPU 缓存友好,而链表节点分散在内存各处,缓存命中率低。

CPU 缓存为什么这么重要?现代 CPU 有三层缓存(L1/L2/L3),访问速度大约是:L1 ~1ns,L2 ~4ns,L3 ~12ns,主内存 ~100ns。当 CPU 从内存读取数据时,会顺便把相邻的一块数据(cache line,通常是 64 字节)一起加载到缓存里。

这对 ArrayList 是巨大的利好:elementData 是一段连续内存,你访问 elementData[0],CPU 顺手就把 elementData[1]elementData[2]......后面的元素也预加载进了缓存。这种"访问一个地址后大概率访问相邻地址"的现象叫空间局部性

LinkedList 正好相反:每个 Node 在堆内存里是独立分配的对象,可能散落在几个完全不相干的地址。你访问 Node A 的时候,CPU 不知道 Node B 在哪,没法预加载。每次跳到下一个节点,都大概率是 cache miss,要等 100ns 从主内存读数据。
#mermaid-svg-NweHFOTbaCaNVEV9{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-NweHFOTbaCaNVEV9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NweHFOTbaCaNVEV9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NweHFOTbaCaNVEV9 .error-icon{fill:#552222;}#mermaid-svg-NweHFOTbaCaNVEV9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NweHFOTbaCaNVEV9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NweHFOTbaCaNVEV9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NweHFOTbaCaNVEV9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NweHFOTbaCaNVEV9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NweHFOTbaCaNVEV9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NweHFOTbaCaNVEV9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NweHFOTbaCaNVEV9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NweHFOTbaCaNVEV9 .marker.cross{stroke:#333333;}#mermaid-svg-NweHFOTbaCaNVEV9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NweHFOTbaCaNVEV9 p{margin:0;}#mermaid-svg-NweHFOTbaCaNVEV9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NweHFOTbaCaNVEV9 .cluster-label text{fill:#333;}#mermaid-svg-NweHFOTbaCaNVEV9 .cluster-label span{color:#333;}#mermaid-svg-NweHFOTbaCaNVEV9 .cluster-label span p{background-color:transparent;}#mermaid-svg-NweHFOTbaCaNVEV9 .label text,#mermaid-svg-NweHFOTbaCaNVEV9 span{fill:#333;color:#333;}#mermaid-svg-NweHFOTbaCaNVEV9 .node rect,#mermaid-svg-NweHFOTbaCaNVEV9 .node circle,#mermaid-svg-NweHFOTbaCaNVEV9 .node ellipse,#mermaid-svg-NweHFOTbaCaNVEV9 .node polygon,#mermaid-svg-NweHFOTbaCaNVEV9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NweHFOTbaCaNVEV9 .rough-node .label text,#mermaid-svg-NweHFOTbaCaNVEV9 .node .label text,#mermaid-svg-NweHFOTbaCaNVEV9 .image-shape .label,#mermaid-svg-NweHFOTbaCaNVEV9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-NweHFOTbaCaNVEV9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NweHFOTbaCaNVEV9 .rough-node .label,#mermaid-svg-NweHFOTbaCaNVEV9 .node .label,#mermaid-svg-NweHFOTbaCaNVEV9 .image-shape .label,#mermaid-svg-NweHFOTbaCaNVEV9 .icon-shape .label{text-align:center;}#mermaid-svg-NweHFOTbaCaNVEV9 .node.clickable{cursor:pointer;}#mermaid-svg-NweHFOTbaCaNVEV9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NweHFOTbaCaNVEV9 .arrowheadPath{fill:#333333;}#mermaid-svg-NweHFOTbaCaNVEV9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NweHFOTbaCaNVEV9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NweHFOTbaCaNVEV9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NweHFOTbaCaNVEV9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NweHFOTbaCaNVEV9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NweHFOTbaCaNVEV9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NweHFOTbaCaNVEV9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NweHFOTbaCaNVEV9 .cluster text{fill:#333;}#mermaid-svg-NweHFOTbaCaNVEV9 .cluster span{color:#333;}#mermaid-svg-NweHFOTbaCaNVEV9 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-NweHFOTbaCaNVEV9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NweHFOTbaCaNVEV9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-NweHFOTbaCaNVEV9 .icon-shape,#mermaid-svg-NweHFOTbaCaNVEV9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NweHFOTbaCaNVEV9 .icon-shape p,#mermaid-svg-NweHFOTbaCaNVEV9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NweHFOTbaCaNVEV9 .icon-shape .label rect,#mermaid-svg-NweHFOTbaCaNVEV9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NweHFOTbaCaNVEV9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NweHFOTbaCaNVEV9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NweHFOTbaCaNVEV9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 缓存失效
每次访问新节点

大概率 cache miss

需要从主内存加载
LinkedList_分散内存
Node0 地址0x1000
Node1 地址0x3A00
Node2 地址0x08F0
Node3 地址0x7B20
CPU缓存
一次加载整个 cache line

多个元素同时进缓存
ArrayList_连续内存

E0

E1

E2

E3

这就是为什么遍历 100 万个元素的 ArrayList 可能只要几毫秒,而 LinkedList 要几十甚至上百毫秒------不是算法复杂度的问题(两者遍历都是 O(n)),而是常数因子差了 10 倍以上。

4. 给定节点删除

这是 LinkedList 真正的优势。比如你遍历链表时拿到某个节点的引用,可以直接通过 prev 和 next 指针把它从链表中摘出来 O(1):

java 复制代码
// LinkedList 内部 remove 节点
E unlink(Node<E> x) {
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}

可以看到完全没有遍历,纯指针操作。

性能差异的直观感受

理论复杂度分析是一回事,实际跑起来的差距是另一回事。下面给出的不是精确 benchmark,而是在典型 JVM 环境下的数量级参考:

操作 ArrayList LinkedList 差距倍数
随机访问 10 万次(按索引 get) ~2ms ~5000ms ~2500x
尾部追加 10 万次 ~3ms ~5ms 接近
头部插入 10 万次 ~800ms ~5ms ~160x
遍历 100 万元素(for 循环) ~3ms ~60ms ~20x
遍历 100 万元素(for-each) ~5ms ~12ms ~2.4x
中间位置插入 1 万次 ~20ms ~45ms ~2x(ArrayList 反而更快)

几个值得注意的点:

随机访问是 ArrayList 的绝对主场。 即使 LinkedList 内部做了"二分方向"优化(从近的一端开始遍历),按索引查还是 O(n)。10 万个元素,平均要遍历 25000 步,而 ArrayList 永远 1 步。

遍历场景,for 循环和 for-each 的差距不同。 ArrayList 两种方式差不多;但 LinkedList 用 for 循环 for (int i=0; i<list.size(); i++) list.get(i) 是灾难------每次 get(i) 都从链表头重新遍历,O(n²)。而 for-each 用的是迭代器,每次 next() 直接走一步,O(n)。所以遍历 LinkedList 必须用 for-each 或迭代器

中间位置插入 ArrayList 反而占优。 虽然 ArrayList 要搬数据(System.arraycopy 是 native 方法,极快),但 LinkedList 先要遍历定位到目标位置。而且 ArrayList 的连续内存在缓存里效率高,LinkedList 遍历定位本身就有大量 cache miss。

内存占用:连续 vs 分散

维度 ArrayList LinkedList
存储方式 连续内存 分散内存
元素存储 只存数据 存数据 + 两个指针(prev, next)
空间利用率 高(但尾部可能有预留容量) 低(每个节点多 2 个引用开销)
扩容开销 需要拷贝整个数组 不需要扩容,逐个添加

ArrayList 因为扩容机制,可能会预留一些空位(比如容量是 15,只装了 12 个元素)。LinkedList 没有预留的概念,加一个就 new 一个 Node,但每个 Node 多了 prev 和 next 两个引用,在 64 位 JVM 上每个引用 8 字节,光这两个指针每个节点就多出 16 字节。

数据量很大的时候,ArrayList 因为内存连续,对 CPU 缓存更友好,实际遍历速度会明显快于 LinkedList。

线程安全

ArrayList 和 LinkedList 都不是线程安全的。它们的方法都没加 synchronized。

如果需要在多线程环境下使用 List,有三种选择:

java 复制代码
// 1. Collections.synchronizedList 包装
List<String> syncList = Collections.synchronizedList(new ArrayList<>());

// 2. CopyOnWriteArrayList(读多写少)
List<String> cowList = new CopyOnWriteArrayList<>();

// 3. 在方法内部使用局部变量------天然线程安全
public void process() {
    List<String> list = new ArrayList<>();  // 局部变量,栈私有
    // ...
}

选型指南

一句话总结选哪个:
#mermaid-svg-TGdarKqYvsvorJeB{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-TGdarKqYvsvorJeB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TGdarKqYvsvorJeB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TGdarKqYvsvorJeB .error-icon{fill:#552222;}#mermaid-svg-TGdarKqYvsvorJeB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TGdarKqYvsvorJeB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TGdarKqYvsvorJeB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TGdarKqYvsvorJeB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TGdarKqYvsvorJeB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TGdarKqYvsvorJeB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TGdarKqYvsvorJeB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TGdarKqYvsvorJeB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TGdarKqYvsvorJeB .marker.cross{stroke:#333333;}#mermaid-svg-TGdarKqYvsvorJeB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TGdarKqYvsvorJeB p{margin:0;}#mermaid-svg-TGdarKqYvsvorJeB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TGdarKqYvsvorJeB .cluster-label text{fill:#333;}#mermaid-svg-TGdarKqYvsvorJeB .cluster-label span{color:#333;}#mermaid-svg-TGdarKqYvsvorJeB .cluster-label span p{background-color:transparent;}#mermaid-svg-TGdarKqYvsvorJeB .label text,#mermaid-svg-TGdarKqYvsvorJeB span{fill:#333;color:#333;}#mermaid-svg-TGdarKqYvsvorJeB .node rect,#mermaid-svg-TGdarKqYvsvorJeB .node circle,#mermaid-svg-TGdarKqYvsvorJeB .node ellipse,#mermaid-svg-TGdarKqYvsvorJeB .node polygon,#mermaid-svg-TGdarKqYvsvorJeB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TGdarKqYvsvorJeB .rough-node .label text,#mermaid-svg-TGdarKqYvsvorJeB .node .label text,#mermaid-svg-TGdarKqYvsvorJeB .image-shape .label,#mermaid-svg-TGdarKqYvsvorJeB .icon-shape .label{text-anchor:middle;}#mermaid-svg-TGdarKqYvsvorJeB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TGdarKqYvsvorJeB .rough-node .label,#mermaid-svg-TGdarKqYvsvorJeB .node .label,#mermaid-svg-TGdarKqYvsvorJeB .image-shape .label,#mermaid-svg-TGdarKqYvsvorJeB .icon-shape .label{text-align:center;}#mermaid-svg-TGdarKqYvsvorJeB .node.clickable{cursor:pointer;}#mermaid-svg-TGdarKqYvsvorJeB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TGdarKqYvsvorJeB .arrowheadPath{fill:#333333;}#mermaid-svg-TGdarKqYvsvorJeB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TGdarKqYvsvorJeB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TGdarKqYvsvorJeB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TGdarKqYvsvorJeB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TGdarKqYvsvorJeB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TGdarKqYvsvorJeB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TGdarKqYvsvorJeB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TGdarKqYvsvorJeB .cluster text{fill:#333;}#mermaid-svg-TGdarKqYvsvorJeB .cluster span{color:#333;}#mermaid-svg-TGdarKqYvsvorJeB 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-TGdarKqYvsvorJeB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TGdarKqYvsvorJeB rect.text{fill:none;stroke-width:0;}#mermaid-svg-TGdarKqYvsvorJeB .icon-shape,#mermaid-svg-TGdarKqYvsvorJeB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TGdarKqYvsvorJeB .icon-shape p,#mermaid-svg-TGdarKqYvsvorJeB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TGdarKqYvsvorJeB .icon-shape .label rect,#mermaid-svg-TGdarKqYvsvorJeB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TGdarKqYvsvorJeB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TGdarKqYvsvorJeB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TGdarKqYvsvorJeB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是







需要什么操作最频繁?
随机访问、遍历
ArrayList
频繁在头部增删?
LinkedList
需要给定节点删除?
LinkedList
数据量很大+遍历多?
ArrayList(缓存友好)
默认用 ArrayList,够用了

绝大多数业务场景下,ArrayList 完全够用。只在确实需要频繁头部增删、或者给定节点删除的场景,再考虑 LinkedList。一个典型场景是实现 LRU 缓存,需要把最近访问的元素移到头部或删除任意位置的元素------但这通常已经用 LinkedHashMap 了。

面试模板

如果面试官问"ArrayList 和 LinkedList 有什么区别",可以这样答:

从四个维度来看。底层数据结构上,ArrayList 是动态数组,内存连续;LinkedList 是双向链表,每个节点存数据和前后指针。

操作效率上,ArrayList 按下标查 O(1),LinkedList 需要遍历 O(n)。增删要分情况------头部增删 LinkedList O(1) 明显快于 ArrayList O(n);尾部增删两者都是 O(1);中间位置增删两者差不多,因为 LinkedList 需要先遍历定位;给定节点删除是 LinkedList 真正的优势 O(1)。

内存上,ArrayList 空间利用率高但可能有预留容量,LinkedList 每个节点多了两个引用开销,但不需要扩容,加一个建一个节点。

线程安全上,两者都不是线程安全的,多线程场景需要 Collections.synchronizedList 包装或用 CopyOnWriteArrayList。

实际开发中,大多数场景默认用 ArrayList 就够了,只在确实需要频繁头部操作或者给定节点增删的特殊场景才用 LinkedList。

相关推荐
组合缺一1 小时前
Solon Server 启动模式深度解析:从 0.3MB 内核到 10+ Server 插件
java·websocket·http·solon·server
雨落在了我的手上1 小时前
初识java(十五):字符串-String类
java·开发语言
小同志001 小时前
application.properties 和 application.yml
java·spring boot·spring·application.yml·.properities
唐青枫1 小时前
Java JdbcTemplate 实战指南:用 Spring 轻量完成数据库增删改查
java·spring boot·spring
未秃头的程序猿1 小时前
别再让大模型单打独斗了!Java 多 Agent 协作实战:任务拆解+结果聚合
java·后端·ai编程
右耳朵猫AI1 小时前
Java & JVM技术周刊 2026年第20周
java·开发语言·jvm
人道领域1 小时前
【LeetCode刷题日记】538.把二叉搜索树转换为累加树
java·开发语言·后端·算法·leetcode
铁皮哥1 小时前
【后端开发】什么是守护线程,和普通线程有什么区别?
java·开发语言·数据库·人工智能·python·spring·intellij-idea
西凉的悲伤2 小时前
Spring Boot + ShardingSphere 介绍
java·spring boot·后端·shardingsphere·分库分表