面试最常问的一个集合对比题就是 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。