Java List 集合详解(ArrayList、LinkedList、Vector)

发布于:2025-08-15 ⋅ 阅读:(18) ⋅ 点赞:(0)

在 Java 中,List 是一个非常常用的接口,它代表有序、可重复的集合。常见的实现类有:

  • ArrayList(基于动态数组,查询快、增删慢)

  • LinkedList(基于双向链表,增删快、查询慢)

  • Vector(早期的线程安全动态数组,现在基本被 ArrayList 替代)

       本篇文章将从 代码示例 + 文字解释 + 原理 + 优缺点 四个方面,带你全面掌握它们的区别与使用场景。


一、ArrayList

关键词: 集合类、动态数组、泛型、安全存储

       在 Java 中,ArrayList 是最常用的集合类之一,它属于 java.util 包,是一个可变长数组——和数组不同的是,它的长度可以动态增加或减少,非常适合在数据量不固定的场景下使用。

下面我们通过一个“城市列表”的案例,逐行讲解常用语法。


1. 创建 ArrayList 并添加元素

import java.util.ArrayList; // 导入 ArrayList 类

public class ArrayListDemo {
    public static void main(String[] args) {

        // 1. 创建 ArrayList 对象(存储 String 类型数据)
        ArrayList<String> cities = new ArrayList<>();

        // 2. 添加城市名称
        cities.add("北京");
        cities.add("上海");
        cities.add("广州");

        // 3. 打印城市列表
        System.out.println("城市列表:" + cities);

        // 4. 获取列表大小
        System.out.println("城市数量:" + cities.size());

        // 5. 根据索引获取元素
        System.out.println("索引 1 的城市是:" + cities.get(1));
    }
}

解释:

  • import java.util.ArrayList;
    作用:导入 Java 提供的 ArrayList 类。
    背景:集合类位于 java.util 包,不导入会编译报错。

  • ArrayList<String> cities = new ArrayList<>();
    作用:创建一个只能存储 StringArrayList
    <String>泛型,让集合类型更安全。

  • cities.add("北京");
    作用:向末尾添加一个元素。
    注意:ArrayList 索引从 0 开始。

  • System.out.println("城市列表:" + cities);
    作用:直接打印列表,会以 [元素1, 元素2, ...] 形式输出。

  • cities.size()
    作用:获取当前元素个数(方法而非属性)。

  • cities.get(1)
    作用:按索引取元素,这里是第二个城市“上海”。
    注意:越界会抛 IndexOutOfBoundsException


2. 在指定位置插入和替换元素

cities.add(1, "深圳");
System.out.println("插入后的列表:" + cities);

cities.set(2, "杭州");
System.out.println("替换后的列表:" + cities);

解释:

  • add(int index, E element):在指定位置插入新元素(原元素后移)。

  • set(int index, E element)替换指定位置的元素。

  • 区别:add() 插入,set() 覆盖。


3. 删除元素

cities.remove(3); // 按索引删除
System.out.println("删除索引3的元素后:" + cities);

cities.remove("深圳"); // 按内容删除
System.out.println("删除深圳后:" + cities);

解释:

  • remove(index)
    根据索引删除元素,后面的元素会自动前移。

  • remove(Object)
    根据元素值删除,匹配到第一个符合的元素就移除。


4. 判断是否存在

System.out.println("是否包含北京:" + cities.contains("北京"));
System.out.println("是否包含天津:" + cities.contains("天津"));

解释

  • contains(Object)
    检查列表中是否包含某个元素,返回 truefalse


5. 遍历 ArrayList

System.out.println("=== for 循环遍历 ===");
for (int i = 0; i < cities.size(); i++) {
    System.out.println("索引 " + i + " : " + cities.get(i));
}

System.out.println("=== 增强 for 遍历 ===");
for (String city : cities) {
    System.out.println(city);
}

System.out.println("=== forEach 方法遍历 ===");
cities.forEach(System.out::println);

解释

  • 普通 for 循环:可通过索引灵活访问元素。

  • 增强 for 循环:更简洁,直接获取元素值。

  • forEach 方法:使用 Lambda 表达式或方法引用遍历。


6. 清空列表

cities.clear();
System.out.println("清空后的城市列表:" + cities);

解释

  • clear()
    移除所有元素,使列表变为空,但对象依然存在,可继续添加元素。

小结

  • 底层结构:动态数组(Object[]),支持自动扩容。

  • 访问速度:按索引访问快(O(1))。

  • 插入/删除:中间插入或删除慢(O(n)),尾部操作快。

  • 线程安全:非线程安全,需要手动同步。

  • 适用场景:读操作多、随机访问频繁,元素数量变化不大时最合适。

  • 常用方法add()add(index, element)set()remove()get()size()contains()clear()


二、LinkedList

关键词:集合类、双向链表、插入删除高效、支持队列/栈

在 Java 中,LinkedList 是基于 双向链表 实现的 List,它属于 java.util 包。
ArrayList 不同,它的优势是在 任意位置插入和删除元素 时效率更高。

下面通过一个“任务队列”的案例,逐行讲解常用语法。


1. 创建 LinkedList 并添加元素

import java.util.LinkedList; // 导入 LinkedList 类

public class LinkedListDemo1 {
    public static void main(String[] args) {
        LinkedList<String> tasks = new LinkedList<>(); // 创建空链表
        tasks.add("写周报");       // 末尾添加
        tasks.add("整理文件");
        tasks.add("开会");

        System.out.println("初始化:" + tasks); // [写周报, 整理文件, 开会]

        tasks.addFirst("检查邮件"); // 头部添加
        tasks.addLast("提交总结");  // 尾部添加
        tasks.add(2, "打印资料");   // 指定索引插入
        System.out.println("添加后的任务列表:" + tasks);

        System.out.println("任务数量:" + tasks.size());
    }
}

解释:

  • LinkedList<String> tasks = new LinkedList<>();
    创建只能存储 String 的链表,底层节点带前驱/后继指针。

  • add(E e)(末尾添加)
    将元素链接到尾节点之后。⏱ 一般为 O(1)

  • addFirst(E e) / addLast(E e)
    头/尾插入;双向链表此操作非常快。⏱ O(1)

  • add(int index, E e)
    在指定位置插入,需要遍历到该位置再改指针。⏱ O(n)

  • size()
    返回元素个数。实现中会维护计数,因此是 ⏱ O(1)

小提示:LinkedList 打印时会调用 toString(),格式如 [元素A, 元素B, ...]


2. 获取元素(按索引 / 首尾 / 非抛异常版)

import java.util.LinkedList;

public class LinkedListDemo2 {
    public static void main(String[] args) {
        LinkedList<String> tasks = new LinkedList<>();
        tasks.add("写周报");
        tasks.add("整理文件");
        tasks.add("开会");

        System.out.println("第一个任务:" + tasks.getFirst()); // 抛异常版
        System.out.println("最后一个任务:" + tasks.getLast());
        System.out.println("索引1的任务:" + tasks.get(1));   // 随机访问

        // 不抛异常的安全获取(空表返回 null)
        System.out.println("peekFirst:" + tasks.peekFirst());
        System.out.println("peekLast :" + tasks.peekLast());
        System.out.println("peek    :" + tasks.peek());      // 等价于 peekFirst
    }
}

解释:

  • getFirst()/getLast()
    获取首/尾元素;空链表时抛 NoSuchElementException。⏱ O(1)

  • get(int index)
    需要从头或尾走到该位置,链表随机访问慢。⏱ O(n)

  • peekFirst()/peekLast()/peek()
    不抛异常的获取版本:空表返回 null,用于更稳健的代码。

易错点:把 getFirst() 当成“安全获取”会在空链表下崩溃,空表请用 peekFirst()


3. 在指定位置插入与替换元素

import java.util.LinkedList;

public class LinkedListDemo3 {
    public static void main(String[] args) {
        LinkedList<String> tasks = new LinkedList<>();
        tasks.add("写周报");
        tasks.add("整理文件");
        tasks.add("开会");

        tasks.addFirst("检查邮件");      // 首部插入
        tasks.add(2, "领取物料");        // 指定索引插入
        String old = tasks.set(1, "打印资料"); // 替换并返回被替换的旧值
        System.out.println("被替换的任务:" + old);
        System.out.println("更新后的任务列表:" + tasks);
    }
}

代码讲解:

  • addFirst(E e) / add(int index, E e)
    插入元素;add(index, e) 需要先定位再改指针。

  • set(int index, E e)
    覆盖指定位置元素并返回旧值。

  • 区别:add(index, e)插入(元素后移),set(index, e)覆盖

边界:索引越界会抛 IndexOutOfBoundsExceptionindex < 0 || index > size【插入】;index >= size【set/get】)。


4. 查询与定位元素

import java.util.LinkedList;

public class LinkedListDemo4 {
    public static void main(String[] args) {
        LinkedList<String> tasks = new LinkedList<>();
        tasks.add("开会");
        tasks.add("写周报");
        tasks.add("开会"); // 重复元素
        tasks.add("整理文件");

        System.out.println("是否包含'开会':" + tasks.contains("开会"));
        System.out.println("第一次出现'开会'的位置:" + tasks.indexOf("开会"));
        System.out.println("最后一次出现'开会'的位置:" + tasks.lastIndexOf("开会"));
    }
}

代码讲解:

  • contains(Object o)
    线性查找是否存在。⏱ O(n)

  • indexOf(Object o) / lastIndexOf(Object o)
    从前/从后查找第一个匹配位置。⏱ O(n)


5. 删除元素(首/尾/索引/值 & 非抛异常版)

import java.util.LinkedList;

public class LinkedListDemo5 {
    public static void main(String[] args) {
        LinkedList<String> tasks = new LinkedList<>();
        tasks.add("检查邮件");
        tasks.add("写周报");
        tasks.add("整理文件");
        tasks.add("开会");

        String first = tasks.removeFirst(); // 删除首元素(抛异常版)
        String last  = tasks.removeLast();  // 删除尾元素
        String byIdx = tasks.remove(1);     // 删除索引1的元素
        boolean byVal = tasks.remove("不存在的任务"); // 按值删除,未删返回 false

        System.out.println("removeFirst:" + first);
        System.out.println("removeLast :" + last);
        System.out.println("remove(1) :" + byIdx);
        System.out.println("remove(o) :" + byVal);
        System.out.println("删除后的任务列表:" + tasks);

        // 不抛异常的删除(空表返回 null)
        tasks.clear();
        System.out.println("pollFirst:" + tasks.pollFirst()); // null
        System.out.println("pollLast :" + tasks.pollLast());  // null
        System.out.println("poll     :" + tasks.poll());      // 等价于 pollFirst
    }
}

代码讲解:

  • removeFirst()/removeLast()/remove(int)
    删除并返回元素;空表/越界会抛异常。

  • remove(Object o)
    删除第一个匹配的元素,返回是否成功。

  • pollFirst()/pollLast()/poll()
    不抛异常的删除:空表返回 null,推荐在未知是否为空时使用。


6. 遍历方式(for / foreach / Iterator / 逆序)

import java.util.Iterator;
import java.util.LinkedList;

public class LinkedListDemo6 {
    public static void main(String[] args) {
        LinkedList<String> tasks = new LinkedList<>();
        tasks.add("写周报");
        tasks.add("整理文件");
        tasks.add("开会");

        // 1) for 索引遍历(不建议:链表随机访问慢)
        for (int i = 0; i < tasks.size(); i++) {
            System.out.println("索引" + i + ":" + tasks.get(i));
        }

        // 2) 增强 for(推荐)
        for (String t : tasks) {
            System.out.println("foreach:" + t);
        }

        // 3) Iterator(可边遍历边安全删除)
        for (Iterator<String> it = tasks.iterator(); it.hasNext(); ) {
            String t = it.next();
            if (t.contains("周报")) it.remove(); // 迭代器删除,避免并发修改异常
        }
        System.out.println("迭代器删除后:" + tasks);

        // 4) 逆序遍历(尾 -> 头)
        Iterator<String> dit = tasks.descendingIterator();
        while (dit.hasNext()) {
            System.out.println("逆序:" + dit.next());
        }
    }
}

代码讲解:

  • 链表不适合 for (i) 访问get(i) 是 O(n)。

  • Iteratorremove()唯一安全的遍历时删除方式;否则易抛 ConcurrentModificationException

  • descendingIterator():从尾到头的逆序迭代。


7. 作为队列 / 栈使用(Deque 能力)

import java.util.LinkedList;

public class LinkedListDemo7 {
    public static void main(String[] args) {
        // 队列:FIFO
        LinkedList<String> queue = new LinkedList<>();
        queue.offer("任务A"); // == addLast
        queue.offer("任务B");
        System.out.println("队首查看:" + queue.peek()); // == peekFirst
        System.out.println("出队:" + queue.poll());      // == pollFirst
        System.out.println("队列状态:" + queue);

        // 栈:LIFO
        LinkedList<String> stack = new LinkedList<>();
        stack.push("命令1"); // == addFirst
        stack.push("命令2");
        System.out.println("栈顶查看:" + stack.peek()); // == peekFirst
        System.out.println("出栈:" + stack.pop());       // == removeFirst
        System.out.println("栈状态:" + stack);
    }
}

代码讲解:

  • 队列 APIoffer(入队)、peek(窥视队首)、poll(出队)。

  • 栈 APIpush(压栈)、peek(窥视栈顶)、pop(出栈)。

  • LinkedList 实现了 Deque 接口,所以可同时充当队列/双端队列/栈


8. 其他常用操作与注意点

import java.util.Arrays;
import java.util.LinkedList;

public class LinkedListDemo8 {
    public static void main(String[] args) {
        LinkedList<String> tasks = new LinkedList<>(Arrays.asList("写周报", "整理文件", "开会"));

        System.out.println("是否为空:" + tasks.isEmpty()); // false
        tasks.removeIf(t -> t.contains("报"));             // 按条件删除
        System.out.println("按条件删除后:" + tasks);

        // 转为数组(保留顺序)
        String[] arr = tasks.toArray(new String[0]);
        System.out.println("数组长度:" + arr.length);
    }
}

代码讲解:

  • isEmpty():是否无元素。

  • removeIf(Predicate):按条件批量删除,Java 8+。

  • toArray(new String[0]):转数组(推荐写法,类型安全)。

提醒:LinkedList 更适合增删多的场景;若主要是队列/栈功能,ArrayDeque 往往更快、更省内存


小结

  • 优点:插入/删除效率高(尤其首尾 O(1));支持丰富的 Deque(队列/栈)操作。

  • 缺点:随机访问慢(get(index) 为 O(n)),比数组结构更占内存。

  • 使用建议

    • 增删多LinkedList(或优先 ArrayDeque 做队列/栈)。

    • 查找多ArrayList

    • 遍历删除请用 Iterator.remove();空表访问用 peek/poll非抛异常方法。


三、Vector(不推荐新项目使用)

关键词:线程安全、同步、性能低于 ArrayList

       Vector 是一个 线程安全 的动态数组类,和 ArrayList 功能类似,但所有对数据的操作方法(如 addremoveset 等)都被 synchronized 修饰,保证了在多线程环境下的安全性。
       缺点是:锁的开销较大,在单线程下性能明显低于 ArrayList
       目前的推荐做法是:用 ArrayList + 自己的同步机制(如 Collections.synchronizedList()CopyOnWriteArrayList)替代。


1. 创建 Vector 并添加元素

import java.util.Vector;

public class VectorDemo {
    public static void main(String[] args) {
        // 创建一个存储字符串的线程安全动态数组
        Vector<String> books = new Vector<>();

        // 添加元素
        books.add("Java 核心技术");
        books.add("算法导论");
        books.add("设计模式");

        // 输出集合内容
        System.out.println("书籍列表:" + books);
        System.out.println("数量:" + books.size());
    }
}

代码讲解

  • Vector<String> books = new Vector<>();
    创建一个线程安全的 Vector,默认容量为 10,超过容量会自动扩容(扩容规则是容量 * 2)。

  • add()
    在集合末尾添加元素,内部会加锁确保多线程下数据不冲突。

  • size()
    返回当前元素的数量。


2. 插入、替换、删除元素

books.add(1, "计算机网络");  // 插入到索引 1
books.set(2, "操作系统");    // 替换索引 2 位置的元素
books.remove("Java 核心技术"); // 按值删除
books.remove(0);              // 按索引删除

System.out.println("更新后的书籍列表:" + books);

代码讲解

  • add(index, element)
    在指定位置插入元素,原位置及后面的元素依次后移。

  • set(index, element)
    替换指定位置的元素。

  • remove(Object)
    删除与参数值相等的第一个元素。

  • remove(index)
    删除指定索引位置的元素。

这些方法与 ArrayList 一致,区别在于 Vector 内部所有方法都加了 synchronized,可以防止多线程同时修改导致数据错乱。


3. 遍历 Vector

// for-each 遍历
for (String book : books) {
    System.out.println(book);
}

// 传统 for 遍历
for (int i = 0; i < books.size(); i++) {
    System.out.println(books.get(i));
}

// 使用 Enumeration(Vector 特有)
import java.util.Enumeration;
Enumeration<String> enumeration = books.elements();
while (enumeration.hasMoreElements()) {
    System.out.println(enumeration.nextElement());
}

代码讲解

  • for-each:最常用的遍历方式,简洁明了。

  • 传统 for:可通过索引访问,方便进行条件判断。

  • EnumerationVector 独有的遍历方式,早期 Java 版本遗留的接口,现在更多使用 Iterator


4. Vector 的线程安全原理

Vector 的线程安全是通过 方法级的 synchronized 修饰实现的,例如 add() 方法源码:

public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}
  • synchronized:方法锁,保证同一时刻只有一个线程能调用该方法。

  • 缺点:锁粒度太大,单线程环境下会导致性能下降。


5. 扩容机制

  • 初始容量为 10

  • 每次扩容为 原容量的 2 倍

  • 如果指定了 initialCapacity 构造参数,初始容量会按你的参数设置。

示例:

Vector<Integer> nums = new Vector<>(5); // 初始容量 5
for (int i = 0; i < 10; i++) {
    nums.add(i);
    System.out.println("容量:" + nums.capacity());
}

小结

  • 优点:线程安全,适合多线程共享数据。

  • 缺点:单线程下性能差,扩容成本高。

  • 适用场景:需要线程安全的少量数据存储(现在很少用,更多用 ArrayList + 自定义同步机制)。


四、List 集合总结对比表

特性 ArrayList LinkedList Vector
底层结构 动态数组(Object[]) 双向链表 动态数组(Object[])
访问速度 快(O(1) 按索引访问) 慢(O(n) 按索引访问) 快(O(1) 按索引访问)
插入/删除 慢(中间插入需要移动元素) 快(只需修改节点指针) 慢(同 ArrayList)
线程安全 否(需手动加锁) 否(需手动加锁) 是(所有方法都有 synchronized)
内存占用 较低(仅存储数据) 较高(需额外存储前驱/后继指针) 较低(仅存储数据)
适用场景 大量读、少量改;随机访问频繁 大量插入/删除,尤其是中间位置 多线程且需要线程安全(现在较少使用)

总结建议

  1. 单线程、大量查询 → 用 ArrayList(性能最好)。

  2. 频繁插入/删除(中间位置) → 用 LinkedList

  3. 多线程必须线程安全 → 用 Vector(或 Collections.synchronizedList(new ArrayList<>()))。

  4. 如果想兼顾性能和安全,可以用 CopyOnWriteArrayList(并发包提供,读多写少时很高效)。