Java List 集合全面解析:ArrayList、LinkedList 与 Vector 的深度对比

一、List 集合核心特性(补充关键细节)

原文提到 List 的 "有序、可重复、支持索引",这里补充:

  • 有序性 :指「插入顺序」与「存储顺序」一致,而非 "排序"(排序需手动调用 Collections.sort());
  • 可重复性 :允许存储 equals() 返回 true 的元素(如两个 new String("Java"));
  • 索引访问 :索引从 0 开始,越界会抛出 IndexOutOfBoundsException(开发中需注意校验)。

二、ArrayList 深度解析(补充底层原理)

1. 底层动态数组的核心机制

ArrayList 基于动态扩容数组实现,核心参数:

  • DEFAULT_CAPACITY:默认初始容量(10);
  • elementData:存储元素的数组(transient 修饰,避免序列化空元素);
  • size:实际元素个数(≠ 数组长度)。

扩容逻辑(面试高频):

  1. 当添加元素时,若数组已满(size == elementData.length),触发扩容;
  2. 扩容公式:newCapacity = oldCapacity + (oldCapacity >> 1)(即扩容 1.5 倍);
  3. 扩容时会创建新数组,复制原数组元素(Arrays.copyOf()),因此频繁扩容会降低性能

优化建议:若已知元素数量,创建 ArrayList 时指定初始容量,避免扩容:

java

运行

复制代码
// 已知要存1000个元素,指定初始容量,避免多次扩容
ArrayList<String> list = new ArrayList<>(1000);
2. 增删效率低的本质原因
  • 查询快 :直接通过索引定位数组元素(elementData[index]),时间复杂度 O (1);
  • 增删慢
    • 新增 / 删除非尾部元素时,需移动后续元素(如在索引 0 插入,需移动所有元素),时间复杂度 O (n);
    • 示例:list.add(0, "A") → 数组中索引 0 后的元素全部后移一位。
3. 易错点:遍历删除元素

ArrayList 遍历删除时,直接用 for 循环会触发 ConcurrentModificationException,正确写法:

java

运行

复制代码
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

// 错误写法:for循环删除 → 抛ConcurrentModificationException
// for (int i = 0; i < list.size(); i++) {
//     if (list.get(i).equals("B")) {
//         list.remove(i);
//     }
// }

// 正确写法1:迭代器删除
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String elem = it.next();
    if (elem.equals("B")) {
        it.remove(); // 迭代器的remove方法,不会触发异常
    }
}

// 正确写法2:Java 8+ removeIf
list.removeIf(elem -> elem.equals("B"));

三、LinkedList 深度解析(补充底层原理)

1. 双向链表的核心结构

LinkedList 基于双向链表实现,每个节点(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;
    }
}
  • 链表头部(first)和尾部(last)有指针,因此操作首尾元素效率极高(O (1));
  • 访问中间元素时,需从头部 / 尾部开始遍历(根据索引距离首尾的远近选择遍历方向),时间复杂度 O (n)。
2. 作为队列 / 栈的实战用法

LinkedList 实现了 Deque 接口,可直接作为队列(FIFO)、栈(LIFO)使用:

java

运行

复制代码
// 1. 作为队列(先进先出)
Deque<String> queue = new LinkedList<>();
queue.offer("A"); // 入队
queue.offer("B");
System.out.println(queue.poll()); // 出队:A
System.out.println(queue.peek()); // 查看队首:B

// 2. 作为栈(后进先出)
Deque<String> stack = new LinkedList<>();
stack.push("A"); // 入栈
stack.push("B");
System.out.println(stack.pop()); // 出栈:B
System.out.println(stack.peek()); // 查看栈顶:A
3. 性能误区:索引访问

很多开发者误以为 LinkedList 也适合索引访问,实际效率极低:

java

运行

复制代码
LinkedList<Integer> list = new LinkedList<>();
for (int i = 0; i < 100000; i++) {
    list.add(i);
}
// 耗时极长:需遍历链表找到索引99999的元素
long start = System.currentTimeMillis();
int num = list.get(99999);
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "ms"); // 约几十ms(ArrayList仅需0ms)

四、Vector 深度解析(补充替代方案)

1. 线程安全的实现方式

Vector 的线程安全是通过给所有方法加 synchronized 关键字实现的:

java

运行

复制代码
// Vector的add方法源码
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}
  • 缺点:方法级同步导致并发效率极低(即使多个线程操作不同元素,也会互斥);
  • 对比:CopyOnWriteArrayList 采用 "写时复制" 策略,读操作无锁,写操作复制新数组,并发效率更高。
2. 现代替代方案(实战推荐)

表格

场景 推荐方案 优势
单线程 + 大量查询 ArrayList 效率最高
单线程 + 大量增删 LinkedList 增删效率高
多线程 + 读多写少 CopyOnWriteArrayList 读无锁,写复制,并发效率高
多线程 + 读写均衡 Collections.synchronizedList(new ArrayList<>()) 通用线程安全包装

CopyOnWriteArrayList 示例

java

运行

复制代码
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteDemo {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        
        // 多线程添加元素
        Runnable task = () -> {
            for (int i = 0; i < 3; i++) {
                list.add(Thread.currentThread().getName() + "-" + i);
            }
        };
        Thread t1 = new Thread(task, "线程1");
        Thread t2 = new Thread(task, "线程2");
        t1.start();
        t2.start();
        
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        // 遍历输出(无需加锁)
        for (String elem : list) {
            System.out.println(elem);
        }
    }
}

五、三大实现类对比(补充性能维度)

原文的对比表格已清晰,这里补充性能细节和选型决策树

1. 增强版对比表格

表格

特性 ArrayList LinkedList Vector
底层结构 动态数组 双向链表 动态数组
线程安全 非线程安全 非线程安全 线程安全(方法级同步)
查询效率 O (1)(随机访问) O (n)(遍历) O (1)(但同步开销大)
增删效率 O (n)(移动元素) O (1)(首尾)/ O (n)(中间) O (n)(移动 + 同步开销)
扩容机制 1.5 倍扩容 无需扩容(链表节点动态创建) 2 倍扩容(默认)
内存占用 连续内存,有冗余空间 离散内存,每个节点多存指针 连续内存,冗余空间更大
迭代器 fail-fast(快速失败) fail-fast fail-fast
推荐使用场景 单线程、查询多、增删少 单线程、增删多(首尾) 几乎不用
现代替代方案 - - CopyOnWriteArrayList
2. 选型决策树(实战快速选择)

预览

查看代码

查询多/增删少

增删多(首尾)

增删多(中间)

选择List实现类

是否多线程环境?

读多写少?

CopyOnWriteArrayList

Collections.synchronizedList

操作类型?

ArrayList

LinkedList

ArrayList

复制代码
flowchart TD
    A[选择List实现类] --> B{是否多线程环境?}
    B -->|是| C{读多写少?}
    C -->|是| D[CopyOnWriteArrayList]
    C -->|否| E[Collections.synchronizedList]
    B -->|否| F{操作类型?}
    F -->|查询多/增删少| G[ArrayList]
    F -->|增删多(首尾)| H[LinkedList]
    F -->|增删多(中间)| I[ArrayList]

查询多/增删少

增删多(首尾)

增删多(中间)

选择List实现类

是否多线程环境?

读多写少?

CopyOnWriteArrayList

Collections.synchronizedList

操作类型?

ArrayList

LinkedList

ArrayList

豆包

你的 AI 助手,助力每日工作学习

六、实战最佳实践(补充开发规范)

  1. 优先使用 ArrayList:日常开发中查询场景远多于增删,ArrayList 是默认选择;
  2. 指定初始容量:创建 ArrayList 时,若已知元素数量,务必指定初始容量(避免扩容);
  3. 避免索引访问 LinkedList:LinkedList 仅适合首尾增删,中间操作优先用 ArrayList;
  4. 遍历删除用迭代器 /removeIf :避免 ConcurrentModificationException
  5. 多线程场景避免 Vector :优先用 CopyOnWriteArrayList 或同步包装器;
  6. 泛型必须指定 :避免原始类型(如 ArrayList),防止类型转换异常。

总结

  1. ArrayList:动态数组实现,查询快、增删慢,单线程查询场景首选,创建时指定初始容量可优化性能;
  2. LinkedList:双向链表实现,首尾增删快、查询慢,仅适用于单线程首尾频繁操作的场景;
  3. Vector :古老的线程安全数组,效率极低,现代开发中已被 CopyOnWriteArrayList 替代;
  4. 选型核心:单线程看操作类型(查询 / 增删),多线程看读写比例(读多写少用 CopyOnWriteArrayList)。
相关推荐
KIKIiiiiiiii2 小时前
微信自动化机器人开发
java·开发语言·人工智能·python·微信·自动化
victory04312 小时前
containerd打包命令 和NFS挂载
java·开发语言
野犬寒鸦2 小时前
从零起步学习计算机操作系统:进程篇(知识扩展提升)
java·服务器·开发语言·后端·面试
※※冰馨※※2 小时前
【QT】System error #1455: 页面文件太小,无法完成操作
开发语言·windows·qt
badhope2 小时前
Python、C、Java 终极对决!谁主沉浮?谁将消亡?
java·c语言·开发语言·javascript·人工智能·python·github
big_rabbit05022 小时前
java面试题总结2
java·开发语言
深蓝轨迹2 小时前
IDEA 常用编辑快捷键清单
java·ide·intellij-idea
武藤一雄2 小时前
WPF Command 设计思想与实现剖析
windows·微软·c#·.net·wpf·.netcore
橘子hhh2 小时前
贫血模型&充血模型
java