基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

ArrayList LinkedList Vector 比较解析

知识点图片

本文对比了ArrayList、LinkedList和Vector。核心区别在于:ArrayList(数组)查询快,LinkedList(链表)两端增删快,而Vector是线程安全的旧版ArrayList,性能较低。

这是一个非常经典的Java面试题。我们来详细地、全方位地比较ArrayList, LinkedList, 和 Vector 的区别。

总览

ArrayList, LinkedList, 和 Vector 都是 java.util.List 接口的实现类,它们都代表一个有序的、可重复的元素集合。它们最核心的区别在于 底层数据结构线程安全性性能表现

特性 ArrayList LinkedList Vector
底层数据结构 动态数组 (Object[]) 双向链表 (Node) 动态数组 (Object[])
线程安全性 非线程安全 非线程安全 线程安全
随机访问 (Get) 非常快 (O(1)) 慢 (O(n)) 非常快 (O(1)),但有同步开销
插入/删除 (Add/Remove) 末尾快 (O(1)),中间/开头慢 (O(n)) 两端快 (O(1)),中间慢 (O(n)) 末尾快,中间/开头慢,且有同步开销
额外接口 RandomAccess Deque (双端队列), Queue RandomAccess
扩容机制 增长 50% (1.5倍) 无需扩容,按需分配节点 默认增长 100% (2倍)
适用场景 频繁的查询遍历操作 频繁的插入删除操作(尤其是在列表两端) 遗留代码或需要线程安全但性能要求不高的场景

详细解析

1. ArrayList

ArrayList 是最常用的 List 实现类。

  • 底层数据结构:基于动态数组Object[])实现。这意味着它在内存中是一块连续的空间。
  • 优点:
    • 查询速度快:由于是连续的内存空间,它实现了 RandomAccess 接口。通过索引(index)访问元素非常快,时间复杂度为 O(1)
    • 遍历速度快:因为内存是连续的,CPU缓存命中率高,遍历效率高。
  • 缺点:
    • 插入和删除慢:在列表的中间或开头插入/删除元素时,需要移动该位置之后的所有元素,时间复杂度为 O(n)
    • 空间浪费:数组的容量是固定的,如果当前容量满了,需要创建一个更大的新数组,并将旧数组的元素复制过去(即扩容),这会带来性能开销。同时,如果数组很大但只存了少量元素,会造成内存浪费。
  • 线程安全性非线程安全。在多线程环境下需要手动处理同步问题,例如使用 Collections.synchronizedList(new ArrayList<>())

2. LinkedList

LinkedList 在特定场景下非常有用。

  • 底层数据结构:基于双向链表实现。每个元素都是一个节点(Node),节点中包含数据、以及指向前一个节点和后一个节点的引用。
  • 优点:
    • 插入和删除速度快:在列表的开头或结尾插入/删除元素非常快,时间复杂度为 O(1),因为只需要修改几个节点的指针引用。
    • 内存利用率高:按需分配内存,没有容量限制和空间浪费的问题。
  • 缺点:
    • 查询速度慢:不支持高效的随机访问。要查找第 i 个元素,必须从头(或尾)开始遍历,时间复杂度为 O(n)
  • 额外功能:它还实现了 Deque(双端队列)接口,因此可以作为栈(Stack)队列(Queue)来使用,提供了 addFirst(), addLast(), removeFirst(), removeLast() 等方法。
  • 线程安全性非线程安全

3. Vector

Vector 是一个遗留类(Legacy Class),从 Java 1.0 就存在了。

  • 底层数据结构:与 ArrayList 一样,基于动态数组实现。
  • 核心特点:
    • 线程安全:它的所有公共方法(如 add(), get(), remove())都使用了 synchronized 关键字进行修饰,这意味着在同一时刻,只有一个线程能访问这些方法。
  • 缺点:
    • 性能差:由于所有方法都加了同步锁,即使在单线程环境下,也会有锁的获取和释放开销,导致其性能远不如 ArrayList
    • 同步粒度粗Vector 对整个方法加锁,锁的粒度太粗。如果你只是想遍历 Vector,它并不会加锁,但如果在遍历的同时有其他线程修改它,仍然会抛出 ConcurrentModificationException。正确的做法是使用 iterator 并在 vector 对象上加锁。
  • 扩容:默认情况下,容量会翻倍(增长100%),而 ArrayList 是增长50%。这可能导致更大的内存浪费。

如何选择?(总结)

  1. 首选 ArrayList: 在绝大多数情况下,ArrayList 都是最佳选择。特别是当你的主要操作是随机访问(通过索引 get())和遍历时,它的性能最好。

    • 场景: 数据查询、列表展示等。
  2. 选择 LinkedList: 当你需要进行大量的头部或尾部插入/删除操作时,LinkedList 是更好的选择。它也适合用作实现栈和队列

    • 场景: 实现一个任务队列、历史记录(后进先出)等。
  3. 尽量避免使用 Vector: Vector 是一个过时的类,由于其性能问题,现在已经很少在新代码中使用了。

    • 如果需要线程安全的List,怎么办?
      • Collections.synchronizedList(new ArrayList<>()): 这是 Vector 的一个更好的替代品。它返回一个包装后的线程安全的 List,其性能和 Vector 相当,但更符合现代Java集合框架的设计。
      • CopyOnWriteArrayList: 这是并发包 (java.util.concurrent) 中的类。它适用于“读多写少”的并发场景。写入操作(add, set, remove)非常昂贵,因为它会复制整个底层数组。但读取操作完全不需要加锁,速度极快且绝对安全。

代码示例

java
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

public class ListComparison {
    public static void main(String[] args) {
        // ArrayList: 最常用,查询快
        List<String> arrayList = new ArrayList<>();
        arrayList.add("Apple");
        arrayList.add("Banana");
        System.out.println("ArrayList get(1): " + arrayList.get(1)); // 非常快

        // LinkedList: 插入删除快,可作队列
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("Cat");
        linkedList.add("Dog");
        linkedList.addFirst("Animal"); // 作为队列使用
        System.out.println("LinkedList first element: " + linkedList.getFirst()); // 快

        // Vector: 线程安全,但已过时
        List<String> vector = new Vector<>();
        vector.add("Java");
        vector.add("Python");
        System.out.println("Vector get(0): " + vector.get(0)); // 比ArrayList慢,因为有同步开销
    }
}
00:00
00:00