Java 集合框架【2】List 体系

925 阅读17分钟

List

List 接口继承自 Collection , 除了继承了 Collection 中的能力,自身拓展了几个默认方法:

// 批量修改操作
default void replaceAll(UnaryOperator<E> operator) 
// 排序,使用的是 Arrays.sort 
default void sort(Comparator<? super E> c) 

// 位置访问操作
E get(int index);
E set(int index, E element);
void add(int index, E element); // Collection 有 add ,这里拓展了指定 index 。
E remove(int index);
int indexOf(Object o);
int lastIndexOf(Object o);

// 迭代器
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);

// 容器
List<E> subList(int fromIndex, int toIndex);

这里简单介绍一下,List 相较于 Collection 拓展的功能:

  • 批量操作增加排序,说明 List 是有序的
  • 位置访问操作,说明 List 是可以进行位置索引的
  • 容器支持了子 List,可以对 List 进行截取。

List 接口的直接实现包括:ArrayList、Vector、LinkedList、CopyOnWriteArrayList。

ArrayList

源代码基于 open jdk:github.com/openjdk/jdk…

ArrayList 是一个动态数组,支持随机访问。允许存储包括 null 的元素。内部的数据结构是数组。除了实现 List 接口外,还提供了一些方法用来处理数组扩容的方法。

它与 Vector 的区别在于,Vector 是同步的,所有操作方法都加了 synchronized 修饰。而 ArrayList 没有。

继承关系

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable 

内部数据结构

private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

transient Object[] elementData; 
private int size;

public ArrayList() {
		this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    }
}

public ArrayList(Collection<? extends E> c) {
		Object[] a = c.toArray();
		if ((size = a.length) != 0) {
				if (c.getClass() == ArrayList.class) {
						elementData = a;
				} else {
						elementData = Arrays.copyOf(a, size, Object[].class);
				}
		} else {
				// replace with empty array.
				elementData = EMPTY_ELEMENTDATA;
		}
}

真正保存数据的是 elementData ,这是一个对象数组。

ArrayList 有三种构造函数:

  1. 无参构造函数,直接指定实际保存数据的 elementData 为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  2. 指定初始容量参数,此时会根据初始容量的值处理:
    • initialCapacity > 0,elementData 为创建初始容量的 Object 数组;
    • initialCapacity = 0,elementData 直接指定为 EMPTY_ELEMENTDATA;
    • initialCapacity < 0,非法初始容量,抛出 IllegalArgumentException;
  3. 以集合为参数构造一个 ArrayList 对象。

添加数据是顺序的在数组尾部添加新元素,当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小。

容量优化

public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}

对外提供的节省内存空间的自动调整容量方法,但 ArrayList 内部并没有自己调用。

将数组的容量调整为当前元素数量的大小,以节省空间。

modCount是一个用于记录数组修改次数的变量,每次对数组结构进行修改时(如添加、删除元素),该计数器都会增加。这里它被增加,表示数组的结构正在被修改。

检查当前数组中元素的数量size是否小于数组的实际容量elementData.length。如果是,说明数组中有未使用的空间,需要进行缩减。

使用Arrays.copyOf方法创建一个新的数组,新数组的长度为当前元素的数量size,并将原数组elementData的内容复制到新数组中。

Arrays.copyOf:

public static <T> T[] copyOf(T[] original, int newLength) {
		return (T[]) copyOf(original, newLength, original.getClass());
}

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
        Math.min(original.length, newLength));
    return copy;
}

System.java

// in class System
@IntrinsicCandidate
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

System.arraycopy 的性能通常优于手动实现的数组遍历复制。这是因为 System.arraycopy 是一个本地(native)方法,它直接由 JVM 的底层实现,通常是 C 或 C++ 编写的,因此它可以利用底层系统的高效操作来执行数组复制。

扩容逻辑

触发扩容逻辑

ensureCapacity 函数用来确保 ArrayList 的内部数组 elementData 至少有足够的容量来存储指定数量的元素。如果没有足够的容量,它会触发数组的扩容。

public void ensureCapacity(int minCapacity) {
    if (minCapacity > elementData.length
        && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
             && minCapacity <= DEFAULT_CAPACITY)) {
        modCount++;
        grow(minCapacity);
    }
}

这个函数是用来供外部调用的。minCapacity 限制小于等于 10。

扩容逻辑

扩容函数 grow:

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData!= DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1 /* preferred growth */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

private Object[] grow() {
		return grow(size + 1);
}

grow 函数中,如果旧的容量大于 0 或 elementData 不是默认容量数组,则通过 ArraysSupport.newLength 函数扩容:

  • minCapacity - oldCapacity 代表最小增长量;
  • oldCapacity >> 1 代表首选增长量,即 oldCapacity / 2

ArraysSupport.newLength 会选择两者更大的一方,另外为了避免整数溢出(整数类型 int 的最大值是 Integer.MAX_VALUE,当 oldLengthminGrowth 相加超过这个值时,会发生溢出,导致结果变成负数),ArraysSupport.newLength 内部会设置一个最大数量限制。

public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;

扩容完成后,通过 Arrays.copyOf 函数将原来的元素复制到新的数组中,并将新数组赋值给 elementData;

这里的扩容计算为,新容量 = 原始容量 + 最小增长量和原始容量的一半两者的取较大一方的值,一般情况下是 1.5 倍原容量扩容。

如果旧容量为等于 0 或 elementData 不是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此时直接创建一个新的数组,其容量为 DEFAULT_CAPACITY(10)和 minCapacity 较大的一方。

即初次扩容,为 10 和 minCapacity 较大的一方

添加元素

添加操作:

    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow(); // grow(size + 1)
        elementData[s] = e;
        size = s + 1;
    }

    /**
     *  添加元素到末尾
     */
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

在这里,每次添加元素,扩容量为 2 * oldSize。


    /**
     * 这个函数是用来给指定位置添加元素用的
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        modCount++;
        final int s;
        Object[] elementData;
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow(); // 当前容量达到最大,扩容
      	// 使用 System.arraycopy 方法将索引位置及其之后的元素向后移动一位
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);
        elementData[index] = element;
        size = s + 1;
    }

    public void addFirst(E element) {
        add(0, element);
    }

    public void addLast(E element) {
        add(element);
    }

rangeCheckForAdd 为添加操作进行检查,确保 index 在合理范围内:

    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

元素移动的重点在 System.arraycopy

 System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);

System.arraycopy 用于将一个数组中的一部分内容复制到同一数组的另一部分。

index位置开始,复制 s - index 个元素到 elementData 数组中从 index + 1 位置开始的地方。然后将 element 赋值给 elementData 数组的 index 位置。

以下是个例子,index = 2, s = 5,s - index = 3:

  • [1, 2, 3, 4, 5]
  • [1, 2] [ _ ] [3, 4, 5]
  • [1, 2] [ 0 ] [3, 4, 5]
  • [1, 2, 0, 3, 4, 5]
删除操作
    public E remove(int index) {
        Objects.checkIndex(index, size);
        final Object[] es = elementData;
        @SuppressWarnings("unchecked") E oldValue = (E) es[index];
        fastRemove(es, index);
        return oldValue;
    }

    public E removeFirst() {
        if (size == 0) {
            throw new NoSuchElementException();
        } else {
            Object[] es = elementData;
            @SuppressWarnings("unchecked") E oldValue = (E) es[0];
            fastRemove(es, 0);
            return oldValue;
        }
    }

    public E removeLast() {
        int last = size - 1;
        if (last < 0) {
            throw new NoSuchElementException();
        } else {
            Object[] es = elementData;
            @SuppressWarnings("unchecked") E oldValue = (E) es[last];
            fastRemove(es, last);
            return oldValue;
        }
    }

移除操作用到了 fastRemove 函数:

    private void fastRemove(Object[] es, int i) {
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);
      	size = newSize
        es[newSize] = null;
    }

和添加操作一样吗都是直接使用 System.arraycopy 函数快速实现删除。

Fail-Fast机制

ArrayList 也采用了快速失败的机制,通过记录 modCount 参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。Fast-Fail 机制本身并不能保证线程安全。

modCount 是一个整数值,记录了 ArrayList 中结构修改的次数。每当对 ArrayList 进行修改操作时(比如添加、删除元素,或者通过 clear() 方法清空列表),modCount 的值会增加。

当你使用 ArrayList 的迭代器时(比如通过 for-eachiterator() 方法),它会记录当前的 modCount 值。如果在遍历过程中,modCount 发生了变化(意味着集合结构被修改了),迭代器会抛出 ConcurrentModificationException 异常,这就是所谓的快速失败机制。

Vector

继承关系

与 ArrayList 完全一致:

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable

内部数据结构

protected Object[] elementData;

public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}

public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

public Vector() {
    this(10);
}

内部数据结构与 ArrayList 相同,默认构造方法设置的容量也是 10。

扩容逻辑

也是从 add 操作入手:

public synchronized boolean add(E e) {
    modCount++;
    add(e, elementData, elementCount);
    return true;
}

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    elementCount = s + 1;
}

同样来自于 grow 系列方法:

private Object[] grow() {
    return grow(elementCount + 1);
}

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = ArraysSupport.newLength(oldCapacity,
            minCapacity - oldCapacity, /* minimum growth */
            capacityIncrement > 0 ? capacityIncrement : oldCapacity
                                       /* preferred growth */);
    return elementData = Arrays.copyOf(elementData, newCapacity);
}

扩容逻辑与 ArrayList 基本一致,但推荐扩容为 capacityIncrement, capacityIncrement 在构造方法中可以设置:

public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}

默认是 0,也就是说默认情况下,扩容是原容量 2 倍扩容(ArrayList 是 1.5 倍)。

与 ArrayList 的区别

最大的区别是 Vector 的操作都加上了 synchronized 关键字:

public synchronized boolean add(E e)

public synchronized E get(int index)
  
public synchronized E set(int index, E element)

所以 Vector 是线程安全的,而 ArrayList 无法保证线程安全。

另一个区别就是默认扩容量不同:Vector 默认是 2 倍,ArrayList 默认是 1.5 倍。

Stack

Vector 有一个子类 Stack,也就是栈结构类型。表示对象的后进先出堆栈。Stack 继承自 Vector ,并拓展了五个允许将容器视为栈结构的操作。 包括常见的 poppush 操作、以及查看栈顶元素的方法、检查栈是否为空的方法以及从栈顶向下进行搜索某个元素,并获取该元素在栈内深度的方法。

但 JDK 更推荐使用 Deque 来实现栈能力。Deque 接口及其实现提供了一组更完整的 LIFO 堆栈操作能力,应该优先考虑使用 Deque 及其实现。例如:

Deque<Integer> stack = new ArrayDeque<Integer>();

LinkedList

github.com/openjdk/jdk…

基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。

继承关系

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  • AbstractSequentialListList 接口的骨架实现(Skeletal Implementation),它提供了一种最小化实现 List 接口所需的努力,可以用作"顺序访问"数据存储(如链表)的后备支持。对于随机访问数据(如数组),应该优先使用 AbstractList 而不是这个类。
  • Deque:双向队列,支持从两端对元素进行插入和移除,后续在 Queue 接口体系详细说明。
  • Cloneable:对象复制。
  • Serializable:序列化能力。

内部数据结构

transient Node<E> first;
transient Node<E> last;

public LinkedList() {
}

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

通过 firstlast 表示双向链表的两个方向的头节点。Node 的数据结构是:

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;
    }
}

添加操作

有两种添加方式,一种是添加到链表尾部,另一种是添加到指定位置

public boolean add(E e) {
    linkLast(e);
    return true;
}

// 将指定的元素插入到列表的指定位置。将当前该位置上的元素(如果有的话)和任何后续的元素都向右移动(索引加一)。
public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}
linkLast

这里用到了 linkLast 方法将元素添加到链表尾部:

void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}
linkBefore

插入到指定位置的添加操作:

void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

这段代码用于在一个链表中的指定节点 succ 之前插入一个新的节点,新节点的元素值为 e。它会创建一个新的节点 newNode,然后将 newNode 插入到 predsucc 之间,修改它们之间的链接关系,使得新节点成为 pred 的后继节点,同时也成为 succ 的前驱节点。

在插入新节点后,还会更新列表的大小 size 和修改计数器 modCount,以确保列表的状态正确并支持快速失败机制。

这里的 succ 参数通过 node(int index) 而来:

// in add()
linkBefore(element, node(index));

查询操作

node 方法中,根据 index 判断是在链表的前半部分还是后半部分,然后在从链表头部或尾部去遍历,提高效率:

Node<E> node(int index) {
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

所以链表操作的时间复杂度基本上就是这个方法的时间复杂度 O(size >> 1)

删除操作

LinkedList 的 remove 操作有很多,对于链表头部和尾部的删除是 removeFirst 和 removeLast:

public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

来自 Deque 的 remove 方法实现:

public E remove() {
    return removeFirst();
}

根据 index 删除的方法:

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

根据对象删除的方法:

public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

这些删除方法,都有关于链表的操作方法。

删除链表尾部
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }
删除链表头部
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }
删除链表中的指定元素
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

线程安全

LinkedList 的所有操作都没有线程安全相关的逻辑,所以也是无法保证线程安全的。但 LinkedList 的操作类方法都有 modCount 计数,支持 Fast-Fail ,确保并发操作时避免对不一致的数据进行操作。

Java的快速失败(fast-fail)机制是一种在集合类上用于检测并发修改的机制。

快速失败机制的实现是通过在每次对集合进行迭代或修改操作时,都会检查集合的修改次数(modCount)。集合的 modCount 是一个计数器,用于记录对集合进行修改的次数。当一个迭代器或者一个线程开始遍历集合时,会记录当前集合的 modCount 值。当接下来发生集合的修改(添加、删除等)时,modCount 值会增加。如果在迭代或访问过程中,发现集合的 modCount 值与记录的 modCount 值不一致,就会抛出 ConcurrentModificationException 异常,以提醒程序有并发修改操作发生,进而避免对不一致的数据继续操作。

CopyOnWriteArrayList

CopyOnWriteArrayList 并不是 Java 集合框架中的成员,而是来自于 java.util.concurrent 框架,即 Java 并发框架,所以自然具备保证线程安全的能力。

继承关系

图片

线程安全

/** 保护所有变量的锁 */
final transient Object lock = new Object();

/** 内部的数据结构,只能通过getArray/setArray访问。 */
private transient volatile Object[] array;

通过 lock 对象配合 synchronized 来提供加锁能力;通过 transient 确保数据不会被序列化;通过 volatile 关键字确保写操作会立即同步到主内存,确保多线程的可见性。另外就是禁止指令重排序,确保有序性。

这里使用 lock 对象配合 synchronized 关键字而不是 ReentrantLock ,是因为这里保护所有可变操作的锁。(在两者都可以使用时,我们稍微偏向于使用内置监视器而不是ReentrantLock。)

早些版本的 JDK 是 ReentrantLock ,openJDK 18 使用 lock 对象配合 synchronized

array 声明为 volatile 可能是因为该变量在多线程环境下会被频繁地读写,并且多个线程之间需要及时地获取到 array 的最新值。使用 volatile 关键字可以确保线程之间对 array 的修改和读取操作是同步的,避免了因为线程缓存导致的数据不一致问题。

需要注意的是,volatile 关键字仅仅保证了可见性和禁止指令重排序,但并不能保证对 array 的复合操作(例如读取-修改-写入)是原子性的。如果需要保证复合操作的原子性,需要使用其他同步机制,例如使用锁(synchronized)或者并发集合类来确保线程安全。

底层数据结构

/** 内部的数据结构,只能通过getArray/setArray访问。 */
private transient volatile Object[] array;

初始化和构造方法

// 创建一个空列表
public CopyOnWriteArrayList() {
    setArray(new Object[0]); 
}
// 创建一个包含集合元素的列表
public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] es;
    if (c.getClass() == CopyOnWriteArrayList.class)
        es = ((CopyOnWriteArrayList<?>)c).getArray();
    else {
        es = c.toArray();
        if (c.getClass() != java.util.ArrayList.class)
            es = Arrays.copyOf(es, es.length, Object[].class);
    }
    setArray(es);
}
// copy 给定的数组创建列表
public CopyOnWriteArrayList(E[] toCopyIn) {
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

构造方法中最后都调用了 setArray() 方法,将数组保存到了属性 array 中。

添加操作

直接添加元素到数组尾部:

public boolean add(E e) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        es = Arrays.copyOf(es, len + 1);
        es[len] = e;
        setArray(es);
        return true;
    }
}

synchronized 确保同步,直接通过 Arrays.copyOf(int[], int) 复制一份容量 + 1 的新数组。

添加到指定位置:

public void add(int index, E element) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException(outOfBounds(index, len));
        Object[] newElements;
        int numMoved = len - index;
        if (numMoved == 0)
            newElements = Arrays.copyOf(es, len + 1);
        else {
            newElements = new Object[len + 1];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index, newElements, index + 1,
                             numMoved);
        }
        newElements[index] = element;
        setArray(newElements);
    }
}

synchronized 确保同步,本质也是通过 System.arraycopy 系列方法将原来的数组拆成两份然后拼接到 newElements 中,最后通过 setArray 更新底层数据结构。

因为是多线程,所以提供了额外的添加方法,来检查数组中是否已经存在某个元素,如果数组中不存在,则添加;否则,不添加,直接返回,可以保证多线程环境下不会重复添加元素。

public boolean addIfAbsent(E e) {
    Object[] snapshot = getArray();
    return indexOfRange(e, snapshot, 0, snapshot.length) < 0
        && addIfAbsent(e, snapshot);
}

private boolean addIfAbsent(E e, Object[] snapshot) {
    synchronized (lock) {
        Object[] current = getArray();
        int len = current.length;
        if (snapshot != current) {
            // Optimize for lost race to another addXXX operation
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                if (current[i] != snapshot[i]
                    && Objects.equals(e, current[i]))
                    return false;
            if (indexOfRange(e, current, common, len) >= 0)
                    return false;
        }
        Object[] newElements = Arrays.copyOf(current, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    }
}

删除操作

public E remove(int index) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        E oldValue = elementAt(es, index);
        int numMoved = len - index - 1;
        Object[] newElements;
        if (numMoved == 0)
            newElements = Arrays.copyOf(es, len - 1);
        else {
            newElements = new Object[len - 1];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index + 1, newElements, index,
                             numMoved);
        }
        setArray(newElements);
        return oldValue;
    }
}

也是通过 System.arraycopy 来重新组成数组的。

查询操作

public E get(int index) {
    return elementAt(getArray(), index);
}

static <E> E elementAt(Object[] a, int index) {
    return (E) a[index];
}

因为底层时数组,所以直接索引。

扩容逻辑

从添加和删除操作可以看出,CopyOnWriteArrayList 都是直接更新容量,然后通过 System.arraycopy 复制,所以扩容每次都是 +1 / -1 。

总结

List 接口下的实现包括:ArrayList、Vector、LinkedList 和 CopyOnWriteArrayList 。它们之间有以下区别:

List 实现底层数据结构扩容机制线程安全
ArrayList数组每次 1.5 倍扩容无法保证线程安全
Vector数组每次 2 倍扩容sychronized 关键字修饰方法
LinkedList双向链表链表无需扩容无法保证线程安全
CopyOnWriteArrayList数组每次根据添加/删除数量扩容Object 对象配合 sychronized 代码块