List详解

116 阅读4分钟

Java List实现类包括ArrayList(动态数组,查询快)、LinkedList(双向链表增删快)、Vector(同步低效)和CopyOnWriteArrayList(写时复制,读多写少高并发),适用于不同线程与操作场景。

一、List 接口核心特性

  • 有序性:元素按插入顺序存储,可通过索引(index)访问。
  • 可重复性:允许存储重复元素。
  • 支持 null:可以包含 null 元素。
  • 动态扩容:大多数实现类自动处理容量扩展。

二、List 主要实现类对比

实现类底层数据结构线程安全随机访问效率插入/删除效率适用场景
ArrayList动态数组非线程安全O(1)O(n)频繁查询、少量修改
LinkedList双向链表非线程安全O(n)O(1)频繁插入删除、无需随机访问
Vector动态数组线程安全O(1)O(n)遗留代码、需同步的动态数组
CopyOnWriteArrayList动态数组(写时复制)线程安全O(1)(读)O(n)(写)高并发读、极少写操作的场景

三、各实现类详解

1. ArrayList
  • 底层数据结构:基于 Object[] elementData 的动态数组。

  • 扩容机制

    • 初始容量:默认 10(无参构造函数初始化时为空数组,首次添加元素时扩容至10)。

    • 扩容规则:当添加元素导致 size + 1 > elementData.length 时触发扩容。

    • 新容量计算newCapacity = oldCapacity + max(所需最小增量, oldCapacity / 2)(即扩容至1.5倍)。

    • 示例代码

      // JDK 17 源码片段(简化)
      private Object[] grow(int minCapacity) {
          int oldCapacity = elementData.length;
          int newCapacity = ArraysSupport.newLength(
              oldCapacity,
              minCapacity - oldCapacity, // 最小增量
              oldCapacity >> 1           // 默认扩容1.5倍
          );
          return Arrays.copyOf(elementData, newCapacity);
      }
      
  • 线程安全:非线程安全,需通过 Collections.synchronizedList() 包装或使用 CopyOnWriteArrayList

  • 优点

    • 随机访问速度快(直接通过索引定位)。
    • 内存连续,缓存友好。
  • 缺点

    • 插入/删除元素需移动后续元素,效率低。
    • 频繁扩容导致内存复制开销。

2. LinkedList
  • 底层数据结构:基于双向链表(Node 节点)。

    private static class Node<E> {
        E item;
        Node<E> prev;
        Node<E> next;
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.prev = prev;
            this.next = next;
        }
    }
    
  • 操作机制

    • 插入/删除:直接调整相邻节点的引用(时间复杂度 O(1))。
    • 随机访问:需从头或尾遍历链表(时间复杂度 O(n))。
  • 线程安全:非线程安全,需外部同步。

  • 优点

    • 头尾插入删除高效。
    • 无需预先分配内存,动态扩展灵活。
  • 缺点

    • 随机访问性能差。
    • 内存占用较高(每个元素需存储前后节点引用)。

3. Vector
  • 底层数据结构:与 ArrayList 类似,基于动态数组。

  • 线程安全:通过 synchronized 关键字实现方法级同步。

    public synchronized boolean add(E e) {
        modCount++;
        add(e, elementData, elementCount);
        return true;
    }
    
  • 扩容机制:默认扩容至原容量的 2 倍(可通过构造函数指定增量)。

  • 优点:线程安全。

  • 缺点

    • 同步锁导致高并发下性能差。
    • 已逐渐被 CopyOnWriteArrayList 或同步包装类取代。

4. CopyOnWriteArrayList
  • 底层数据结构:基于 volatile Object[] array 的动态数组。

  • 写时复制(COW)机制

    • 写操作(如 addset)时,加锁并复制原数组到新数组,修改后替换原数组引用。

      public boolean add(E e) {
          final ReentrantLock lock = this.lock;
          lock.lock();
          try {
              Object[] elements = getArray();
              int len = elements.length;
              Object[] newElements = Arrays.copyOf(elements, len + 1);
              newElements[len] = e;
              setArray(newElements); // 原子替换数组引用
              return true;
          } finally {
              lock.unlock();
          }
      }
      
    • 读操作:直接访问当前数组(无锁,读性能极高)。

  • 线程安全:通过写时复制和 ReentrantLock 保证线程安全。

  • 优点

    • 读操作完全无锁,适合读多写少场景。
    • 迭代器不会抛出 ConcurrentModificationException
  • 缺点

    • 写操作内存开销大(需复制整个数组)。
    • 数据一致性弱(迭代器访问的是旧数组快照)。

四、扩容机制对比

实现类初始容量扩容规则扩容触发条件
ArrayList10扩容至1.5倍size + 1 > elementData.length
Vector10扩容至2倍(可指定增量)size + 1 > elementData.length
CopyOnWriteArrayList0每次写操作复制数组并扩展1每次添加元素时触发

五、线程安全方案对比

方案实现方式适用场景性能影响
Vector方法级同步(synchronized)低并发简单同步需求高锁竞争,性能差
Collections.synchronizedList包装类加锁(对象锁)简单同步需求锁粒度粗,性能中等
CopyOnWriteArrayList写时复制 + ReentrantLock读多写极少的高并发场景写操作开销大,读无锁

六、选择建议

  • 单线程环境

    • 频繁查询ArrayList
    • 频繁插入删除LinkedList
  • 多线程环境

    • 读多写少CopyOnWriteArrayList
    • 写操作频繁Collections.synchronizedList(new ArrayList<>()) 或并发队列替代。
  • 遗留系统维护Vector(不推荐新项目使用)。


七、示例代码:ArrayList 扩容流程

ArrayList<Integer> list = new ArrayList<>(3); // 初始容量3
list.add(1); // 容量足够,无需扩容
list.add(2);
list.add(3);
list.add(4); // 触发扩容:newCapacity = 3 + 3/2 = 4.5 → 取整为4
// 新数组容量为4,旧数组元素复制到新数组

八、总结

  • ArrayList:适合随机访问,内存高效,但插入删除性能差。
  • LinkedList:适合频繁插入删除,但内存占用高。
  • Vector:线程安全但性能低,已过时。
  • CopyOnWriteArrayList:高并发读场景首选,但写操作代价高。