LinkedList 和ArrayList 的区别?

目录

核心概括

详细对比表格

深入原理分析

1. ArrayList:动态数组

2. LinkedList:双向链表

数据结构差异

时间复杂度对比

内存占用与扩容

适用场景

选择 ArrayList 的情况(绝大多数场景):

选择 LinkedList 的情况:

线程安全


核心概括

  • ArrayList:底层是动态数组。像一辆在轨道上运行的列车,车厢(元素)在内存中连续排列。

  • LinkedList:底层是双向链表。像一队手拉手的人,每个人(节点)都知道自己前后是谁,但他们在内存中的位置是分散的。

详细对比表格

深入原理分析

1. ArrayList:动态数组
  • 工作原理

    1. 内部维护一个 Object[] elementData 数组。

    2. 当使用 get(int index) 时,直接通过数组下标 elementData[index] 访问,所以速度极快。

    3. 当在列表中间或开头插入元素时(add(int index, E element)),例如在位置 i 插入,需要将 i 之后的所有元素都向后移动一位,这是一个 O(n) 操作。

    4. 当数组容量不足时,会创建一个新的更大的数组(通常是原来的1.5倍),并将旧数组的数据拷贝过去,这个过程称为扩容,性能开销较大。

  • 代码示例:体现插入的劣势

ArrayList<Integer> arrayList = new ArrayList<>();
// 先添加100000个元素
for (int i = 0; i < 100000; i++) {
    arrayList.add(i);
}

long start = System.currentTimeMillis();
arrayList.add(0, -1); // 在开头插入一个元素,需要移动后面所有元素!
long end = System.currentTimeMillis();
System.out.println("ArrayList time: " + (end - start) + "ms");
2. LinkedList:双向链表
  • 工作原理

    1. 内部定义了一个 Node 节点类,包含 item(数据)、next(指向下一个节点)、prev(指向上一个节点)。

    2. 当需要访问第 index 个元素时,get(int index) 方法会判断 index 更靠近头部还是尾部,然后从头或尾开始遍历,直到找到目标位置。这是一个 O(n) 操作。

    3. 当在已知位置插入或删除元素时,例如在节点 A 和 B 之间插入节点 C,只需要修改引用:

      • A.next = C

      • C.prev = A

      • C.next = B

      • B.prev = C
        这个操作是 O(1) 的。

  • 代码示例:体现插入的优势

LinkedList<Integer> linkedList = new LinkedList<>();
// 先添加100000个元素
for (int i = 0; i < 100000; i++) {
    linkedList.add(i);
}

long start = System.currentTimeMillis();
linkedList.add(0, -1); // 在开头插入,只需修改第一个节点的prev和新节点的next
long end = System.currentTimeMillis();
System.out.println("LinkedList time: " + (end - start) + "ms");

数据结构差异

LinkedList 基于双向链表实现,每个元素(节点)存储数据及前后节点的引用。
ArrayList 基于动态数组实现,元素在内存中连续存储,通过索引直接访问。

时间复杂度对比

插入/删除操作
LinkedList 在头尾插入/删除时间复杂度为 O(1),中间位置为 O(n)(需遍历定位)。
ArrayList 尾部插入/删除为 O(1),中间或头部操作可能导致数组移动,平均为 O(n)。

随机访问
LinkedList 需遍历节点,时间复杂度 O(n)。
ArrayList 通过索引直接访问,时间复杂度 O(1)。

内存占用与扩容

LinkedList 每个节点需额外存储前后指针,内存开销较大。
ArrayList 内存连续,但动态扩容时需拷贝数据到新数组(默认扩容 1.5 倍)。

适用场景

选择 ArrayList 的情况(绝大多数场景):
  • 频繁按索引访问元素get 和 set 操作)。

  • 大部分操作是在列表的末尾进行添加和删除(add(E element), remove(int index) 且 index 很大)。

  • 对内存空间敏感,希望占用更少的内存。

结论:由于在开发中查询的次数远大于增删,所以 ArrayList 是更常用的选择。

选择 LinkedList 的情况:
  • 需要频繁在列表的中间或开头进行插入和删除操作。

  • 需要将列表当作栈、队列或双端队列来使用(因为它实现了 Deque 接口)。

  • 不确定列表的大小,且非常频繁地进行动态增删,无法接受 ArrayList 扩容带来的性能损耗。

线程安全

两者均非线程安全,需通过 Collections.synchronizedList 或并发容器(如 CopyOnWriteArrayList)实现同步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值