ArrayList 和 LinkedList 的区别?一篇讲透,从此开发和面试都不再纠结

在日常Java开发中,我们几乎每天都在使用List 。

而Java中最常用的两个列表实现就是 ArrayListLinkedList。面试官也是,非常喜欢问它们有什么区别。

很多朋友会死记硬背:"ArrayList 查询快,增删慢; LinkedList 查询慢,增删快。"

但你知道为什么吗?今天我们来深入的了解一下。


一、举个例子

你有一排书架:

ArrayList 就像一整排连续的书柜

所有书按顺序紧挨着放,编号 0、1、2......你想拿第5本书时,直接走过去拿就行,非常快!

LinkedList 就像用绳子串连起来的一堆独立小盒子

每个盒子里除了书,还写着前一个盒子在哪和后一个盒子在哪。你想拿第5本书时,得从第一个盒子开始,一个一个顺着绳子找过去。

下面我们再从技术角度进行深入分析。


二、底层结构:数组 vs 链表

ArrayList

ArrayList 底层是动态数组,元素在内存中是连续存放的。

java 复制代码
// ArrayList简化版原理
public class ArrayList {
    private Object[] elementData; // 核心数组
    private int size; // 当前元素个数
    
    // 添加元素时,如果数组满了会自动扩容
    public void add(Object element) {
        if (size == elementData.length) {
            // 创建一个1.5倍大的新数组,然后拷贝数据
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
        elementData[size++] = element;
    }
}

特点:

  • 内存中连续存储,像一排整齐的座位
  • 自动扩容,但扩容时需要拷贝所有数据
  • 访问速度快,但插入删除可能较慢

LinkedList

LinkedList 的底层是双向链表,元素在内存中是分散存放的,每个元素(节点)都记录着它前后邻居的"地址"。

java 复制代码
// LinkedList节点结构
class Node {
    E item;        // 当前节点数据
    Node next;     // 指向下一个节点
    Node prev;     // 指向上一个节点
}

// LinkedList简化版原理
public class LinkedList {
    private Node first; // 头节点
    private Node last;  // 尾节点
    private int size;
}

每个元素(节点)都保存着前后邻居的信息,就像每个人都知道自己前后是谁。


三、性能对比

我们从三个最常用的操作来看:

1. 随机访问(比如 list.get(100))

ArrayList:O(1)

直接跳到第 100 个位置,秒取!

LinkedList:O(n)

得从头开始数 100 次才能拿到。

结论 :如果你经常通过下标取数据(比如 for 循环遍历),ArrayList要快得多!


2. 在末尾添加/删除(list.add(obj) 或 remove(last))

ArrayList:平均 O(1)

正常情况直接加在最后;但如果空间满了,要扩容(复制整个数组),这时会慢一下(但平均下来还是很快)。

LinkedList:O(1)

直接在尾巴上挂新节点,永远不慢。

结论 :两者在尾部操作都很快,但 ArrayList 还是占优势(因为内存连续,CPU缓存友好)。


3. 在中间或开头插入/删除(比如 list.add(0, obj))

ArrayList:O(n)

插入第 0 位?后面所有元素都要往后挪一位!删也一样,前面空了,后面的要往前挤。

LinkedList:理论上 O(1),但实际常是 O(n)

虽然插入本身只要改指针,但你要先找到第 0 个节点啊!找的过程就要从头遍历,所以整体还是 O(n)。

重要提醒: 很多人以为 LinkedList 在中间插入更快,其实只有当你已经有那个位置的引用时才快(比如用 Iterator 遍历时删除)。

如果只是用 list.add(500, obj),它照样要先找第 500 个节点,速度并不比 ArrayList 快!


四、内存占用

ArrayList

只存数据和一点预留空间(比如容量 10,只用了 6,那有 4 个空位)。

LinkedList

每个元素都要额外存两个指针(指向前一个和后一个节点),内存开销大很多

举个例子:存1000个 Integer 对象:

  • ArrayList:约4KB(假设每个 int 4 字节)
  • LinkedList:约4KB(数据)+ 8KB(指针,每个指针 8 字节 × 2 × 1000)= 12KB+

结论ArrayList 更省内存,尤其存大量小对象时优势更加明显。


五、其他细节补充

是否支持"快速随机访问"?

  • ArrayList 实现了 RandomAccess 接口(这是一个标记接口,告诉程序员:"我支持快速随机访问")。
  • LinkedList 没有实现它。

有些工具类(如 Collections.binarySearch)会根据这个接口选择不同算法,进一步优化性能。

线程安全吗?

都不安全! 多线程环境下直接用会出问题。

如果需要线程安全,可以用:

java 复制代码
List<String> safeList = Collections.synchronizedList(new ArrayList<>());

或者用 CopyOnWriteArrayList(适合读多写少的场景)。

LinkedList 还能当队列用!

LinkedList 实现了 Deque 接口,可以当双端队列用:

java 复制代码
LinkedList<String> queue = new LinkedList<>();
queue.addFirst("A");
queue.addLast("B");
String first = queue.removeFirst(); // A

这时候它的链表结构就非常合适!


六、开发时该用哪个?

使用场景 推荐选择 原因
经常通过下标访问(如 for 循环、get(i)) ArrayList 随机访问快
主要在末尾增删(如日志记录、追加数据) ArrayList 简单高效,缓存友好
需要频繁在已知位置插入/删除(如用 Iterator 遍历时删元素) LinkedList 指针操作快
要实现队列、栈、双端队列 LinkedList 原生支持 addFirst/removeLast
内存紧张 or 存大量小对象 ArrayList 内存更紧凑

一般情况下,默认选 ArrayList 就对了!

只有在明确需要链表特性(如双端操作)时,才考虑 LinkedList。


总结

ArrayList 底层是数组,内存连续,访问快(O(1)),中间增删慢(O(n))。

LinkedList 底层是链表,内存分散,访问慢(O(n)),头尾增删快(O(1))。

日常开发中,优先选 ArrayList 准没错!

只有当你真的需要频繁在列表头尾增删、或者用它当队列/栈时,再考虑 LinkedList

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《代码里全是 new 对象,真的很 Low 吗?我认真想了一晚》

《Java 开发必看:什么时候用 for,什么时候用 Stream?》

《这 5 个冷门 HTML 标签,让我直接删了100 行 JS 代码》

《Vue 组件通信的 8 种最佳实践,你知道几种?》

相关推荐
神奇小汤圆19 分钟前
金三银四Java面试题及答案汇总(2026持续更新)
后端
Ray Liang26 分钟前
用六边形架构与整洁架构对比是伪命题?
java·python·c#·架构设计
颜酱28 分钟前
理解二叉树最近公共祖先(LCA):从基础到变种解析
javascript·后端·算法
神奇小汤圆33 分钟前
加了 limit 1,查询竟然变慢了?
后端
Java水解38 分钟前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端
Java水解42 分钟前
Java 中间件:Dubbo 服务降级(Mock 机制)
java·后端
千寻girling44 分钟前
一份不可多得的 《 Python 》语言教程
人工智能·后端·python
南风9991 小时前
Claude code安装使用保姆级教程
后端
爱泡脚的鸡腿1 小时前
Node.js 拓展
前端·后端
蚂蚁背大象2 小时前
Rust 所有权系统是为了解决什么问题
后端·rust