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 种最佳实践,你知道几种?》

相关推荐
回家路上绕了弯2 小时前
分布式系统重试策略详解:可靠性与资源消耗的平衡艺术
分布式·后端
王中阳Go2 小时前
别再卷 Python 了!Go + 字节 Eino 框架,才是后端人转 AI 的降维打击(附源码)
后端·面试·go
superman超哥2 小时前
Rust 表达式与语句的区别:函数式思维与控制流设计
开发语言·后端·rust·rust表达式·rust语句·函数式思维·控制流设计
fliter2 小时前
常见的链上攻击向量
后端
趁月色小酌***2 小时前
JAVA 知识点总结5
java·开发语言·python
caesar_lion2 小时前
C++ 多线程陷阱:分离线程(detached thread)访问已析构对象的致命隐患
后端
冰冰菜的扣jio2 小时前
InnoDB对于MVCC的实现
java·数据库·sql
Macbethad2 小时前
SpringMVC RESTful API开发技术报告
java·spring boot·后端
05大叔2 小时前
SpringMVCDay01
java·开发语言