Skip to content

Java 集合 - List 篇


List、Set、Map 关系图

Vector

同步的(读写) 可增长的数组存储结构。
默认初始容量为 10 个,可自定义初始容量、扩容时新增容量

扩容逻辑:

  • 若未指定扩容时新增容量,则扩容为原容量的 2 倍
  • 若指定了扩容时新增容量,则扩容为原容量 + 新增容量
  • 若扩容后容量仍不满足,则扩容容量为 插入后元素个数
  • 若扩容后容量【未超过】额定最大容量(Integer.MAX_VALUE - 8),使用 扩容后的容量大小 为新容器大小
  • 若扩容后容量【超过】额定最大容量,且插入后元素个数【超过】额定最大容量,使用 Integer.MAX_VALUE 为新容器大小
  • 若扩容后容量【超过】额定最大容量,且插入后元素个数【未超过】额定最大容量,使用 额定最大容量 为新容器大小
扩容逻辑伪代码
java
int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

public int newCapacity(originalCapacity){
    //插入后元素个数
    int insertAfterCount = originalCapacity + 1;
    //若指定了扩容时新增容量,则扩容为原容量 + 新增容量;否则扩容为原容量的 2 倍
    int newCapacity = increment > 0 ? originalCapacity + increment : originalCapacity * 2;
    //若扩容后容量仍不满足,则扩容容量为 插入后元素个数
    if (newCapacity <= insertAfterCount){
        newCapacity = insertAfterCount;
    }
    //扩容后满足存储,且扩容容量未超过额定最大容量,则使用扩容后的容量。
    if (newCapacity <= MAX_ARRAY_SIZE){
        return newCapacity;
    }
    //扩容后满足存储,但超过了额定最大容量,若插入后元素个数超出额定最大容量,则设定容器容量为 Integer.MAX_VALUE,否则为额定最大容量
    return insertAfterCount > MAX_ARRAY_SIZE ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

删除、清空元素时,不会缩容。
如果想缩容,需自行调用 trimToSize(),将容量缩小至实际存储元素个数

  • 删除元素时,移动数组元素,将末尾元素置为 null
  • 清空元素时,直接将全部元素置为 null

存储结构

java
public class Vector<E> extends AbstractList<E> 
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    protected Object[] elementData;

    // 默认存储大小为 10 个
    public Vector() { this(10); }

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

    /**
     * @param initialCapacity: 初始容量
     * @param capacityIncrement: 扩容容量(每次扩容时新增容器大小)
     */
    public Vector(int initialCapacity, int capacityIncrement) {
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
}

插入元素(发生扩容)

java
public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1); //检测是否扩容
    elementData[elementCount++] = obj;
}

private void ensureCapacityHelper(int minCapacity) {
    // 若添加后超出容器容量,则扩容
    if (minCapacity - elementData.length > 0){
        grow(minCapacity);
    }   
}

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    //扩容后大小 = 原容器大小 + 扩容容量大小(默认为原来一倍)
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
    //扩容后的容器大小仍不满足,则直接使用元素总量
    if (newCapacity - minCapacity < 0){
        newCapacity = minCapacity;
    }
    //新容器超出额定最大容量(Integer.MAX_VALUE - 8)
    // 若元素总量超出额定最大容量,则设定容器容量为 Integer.MAX_VALUE,否则为额定最大容量
    if (newCapacity - MAX_ARRAY_SIZE > 0){
        newCapacity = hugeCapacity(minCapacity);
    }
    //将原数据 copy 至新容器
    elementData = Arrays.copyOf(elementData, newCapacity);
}

删除/清空元素

java
public synchronized E remove(int index) {
    modCount++;
    E oldValue = elementData(index);

    int numMoved = elementCount - index - 1;
    if (numMoved > 0){
        //迁移数组元素
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    }
    //末尾元素置为 null
    elementData[--elementCount] = null;

    return oldValue;
}
java
public void clear() {
    removeAllElements();
}

public synchronized void removeAllElements() {
    final Object[] es = elementData;
    //将数组元素全部置为 null
    for (int to = elementCount, i = elementCount = 0; i < to; i++){
        es[i] = null;
    }
    modCount++;
}

ArrayList

非同步的 可增长的数组存储结构(Vector 的非同步版本)
默认初始容量为 0,可自定义初始容量

扩容逻辑:

  • 扩容为原容量的 1.5 倍
  • 若扩容后容量仍不满足,且容器通过无参构造函数创建,则扩容容量为 Max(10, 插入后元素个数)
  • 若扩容后容量仍不满足,且容器通过有参构造函数创建,则扩容容量为 插入后元素个数
  • 若扩容后容量【未超过】额定最大容量(Integer.MAX_VALUE - 8),使用 扩容后的容量大小 为新容器大小
  • 若扩容后容量【超过】额定最大容量,且插入后元素个数【超过】额定最大容量,使用 Integer.MAX_VALUE 为新容器大小
  • 若扩容后容量【超过】额定最大容量,且插入后元素个数【未超过】额定最大容量,使用 额定最大容量 为新容器大小
扩容逻辑伪代码
java
int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

public int newCapacity(originalCapacity){
    //插入后元素个数
    int insertAfterCount = originalCapacity + 1;
    //扩容原来的 1.5 倍
    int newCapacity = originalCapacity + originalCapacity / 2;
    //扩容后仍不满足
    if (newCapacity <= insertAfterCount){
        //若通过无参构造函数创建,则扩容容量从 10 与 插入后元素个数 中取最大值;否则扩容容量为 插入后元素个数。
        return NoParamsConstructor ? Max(10, insertAfterCount) : insertAfterCount;
    }
    //扩容后满足存储,且扩容容量未超过额定最大容量,则使用扩容后的容量。
    if (newCapacity <= MAX_ARRAY_SIZE){
        return newCapacity;
    }
    //扩容后满足存储,但超过了额定最大容量,若插入后元素个数超出额定最大容量,则设定容器容量为 Integer.MAX_VALUE,否则为额定最大容量
    return insertAfterCount > MAX_ARRAY_SIZE ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

删除、清空元素时,不会缩容。
如果想缩容,需自行调用 trimToSize(),将容量缩小至实际存储元素个数

  • 删除元素时,移动数组元素,将末尾元素置为 null
  • 清空元素时,直接将全部元素置为 null

存储结构

java
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    transient Object[] elementData;

    public ArrayList() {
        // Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

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

插入元素(发生扩容)

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

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length){
        // 扩容。实际调用 grow(size + 1);
        elementData = grow();
    }
    elementData[s] = e;
    size = s + 1;
}

private Object[] grow(int minCapacity) {
    // 扩容 & 将原数据 copy 至新容器
    return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
}

private int newCapacity(int minCapacity) {
    int oldCapacity = elementData.length;
    //扩容后大小 = 原容器大小 + 原容器大小 / 2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //新容器大小仍不满足
    //  若为无参构造函数创建, 则从 默认大小(10个) 与 插入后元素个数 取最大的一个
    //  否则,使用插入后元素个数
    if (newCapacity - minCapacity <= 0) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    //新容器未超出额定最大容量,则使用新容器的容量大小
    //新容器超出额定最大容量(Integer.MAX_VALUE - 8)
    // 若元素总量超出额定最大容量,则设定容器容量为 Integer.MAX_VALUE,否则为额定最大容量
    return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity);
}

删除/清空元素

java
public E remove(int index) {
    Objects.checkIndex(index, size);
    final Object[] es = elementData;
    //真正删除元素
    fastRemove(es, index);
    return oldValue;
}

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);
    }
    //将末尾元素置为 null
    es[size = newSize] = null;
}
java
public void clear() {
    modCount++;
    final Object[] es = elementData;
    //全部置为 null
    for (int to = size, i = size = 0; i < to; i++){
         es[i] = null;
    }     
}

LinkedList

非同步的双向链表存储结构
无容量限制
新增元素为双向链表插入节点操作
删除元素为双向链表移除节点操作

存储结构

java
public class LinkedList<E> extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    // 双向链表的头节点、尾节点
    transient Node<E> first;
    transient Node<E> last;
    public LinkedList() {}

    //私有静态内部类
    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;
        }
    }
}

新增元素

java
// 尾插入 offer() 实际调用的 add()
public boolean add(E e) {
    linkLast(e);
    return true;
}

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

//头插入 push() 实际调用的 addFirst()
public void addFirst(E e) {
    linkFirst(e);
}

private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    //若当前没有元素,则新插入的元素,既是头节点,又是尾节点
    //若已有元素,则让原头节点的prev节点指向新插入元素,新插入元素成为头节点
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

删除/清空元素

java
//删除头节点 pop()、poll()、remove() 都是删除头节点
public E pop() {
    return removeFirst();
}

public E removeFirst() {
    final Node<E> f = first;
    return unlinkFirst(f);
}

private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next; //获取目标节点指向的下个节点
    f.item = null; //将节点持有的数据置为 null,方便 gc
    f.next = null; //切断目标节点与下个节点的关联
    first = next; //让目标节点的下个节点作为头节点
    if (next == null)
        last = null; //目标节点就是最后一个节点,则头节点、尾节点都置为 null
    else
        next.prev = null; //切断下个节点与目标节点的关联
    size--;
    modCount++;
    return element;
}

//删除尾节点
public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

private E unlinkLast(Node<E> l) {
    final E element = l.item;
    final Node<E> prev = l.prev; //获取目标节点指向的上个节点
    l.item = null; //将节点持有的数据置为 null,方便 gc
    l.prev = null; //切断目标节点与上个节点的关联
    last = prev; //让目标节点的上个节点作为尾节点
    if (prev == null)
        first = null; //目标节点就是最后一个节点,则头节点、尾节点都置为 null
    else
        prev.next = null; //切断上个节点与目标节点的关联
    size--;
    modCount++;
    return element;
}
java
public void clear() {
    //遍历所有节点,全部置为 null,方便gc
    for (Node<E> x = first; x != null; ) {
        Node<E> next = x.next;
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    first = last = null;
    size = 0;
    modCount++;
}

CopyOnWriteArrayList

同步的 可增长的数组存储结构。
初始容量为 0
新增元素时,都会创建新容器,并 copy 原容器中的元素
删除元素时,都会创建新的容器,并 copy 原容器中的元素
其与 Vector 的区别:

  • 锁控制的范围不同:虽然都支持线程安全,但 Vector 对所有元素操作都加锁(synchronized);而 CopyOnWriteArrayList 对获取操作是不加锁的。
  • 初始容量与扩容大小不同:Vector 有初始容量,且每次扩容都是 2 倍或指定的新增容量;而 CopyOnWriteArrayList 初始容量为0,且每次扩容仅 +1。
  • 缩容:Vector 删除元素后不会缩容,需自行调用 trimToSize(); 而 CopyOnWriteArrayList 每次操作都是基于 copy,所以会自动缩容。

存储结构

java
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    private transient volatile Object[] array;
    
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    final void setArray(Object[] a) {
        array = a;
    }
}

新增元素

java
public boolean add(E e) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        es = Arrays.copyOf(es, len + 1); //扩容,原容量+1,并将原数组元素全部 copy 过来
        es[len] = e;
        setArray(es);
        return true;
    }
}

删除/清空元素

java
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 {
            //创建新数组,把要移除元素的前面所有元素和后面所有元素分别 copy 至新数组中
            newElements = new Object[len - 1];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index + 1, newElements, index, numMoved);
        }
        setArray(newElements);
        return oldValue;
    }
}

拓展

Collections # synchronized

可通过 Collections # synchronized 系列方法,生成一个线程安全的容器。其原理:创建一个 SynchronizedCollection 用来接收之前的值,并且所有的添加、删除等方法上都加了对象锁。

java
//举例
Set set = Collections.synchronizedSet(new HashSet);

//原理:
public static <T> Set<T> synchronizedSet(Set<T> s) {
    return new SynchronizedSet<>(s);
}

static class SynchronizedSet<E> extends SynchronizedCollection<E>
      implements Set<E> {

    SynchronizedSet(Set<E> s) {
        super(s);
    }

    public boolean equals(Object o) {
        if (this == o)
            return true;
        synchronized (mutex) {return c.equals(o);}
    }
          
    public int hashCode() {
        synchronized (mutex) {return c.hashCode();}
    }
}

static class SynchronizedCollection<E> implements Collection<E>, Serializable {
    final Collection<E> c;  // Backing Collection
    final Object mutex;     // Object on which to synchronize

    SynchronizedCollection(Collection<E> c) {
        this.c = Objects.requireNonNull(c);
        mutex = this;
    }

    public boolean add(E e) {
        synchronized (mutex) {return c.add(e);}
    }
    public boolean remove(Object o) {
        synchronized (mutex) {return c.remove(o);}
    }
    public int size() {
        synchronized (mutex) {return c.size();}
    }
    public boolean isEmpty() {
        synchronized (mutex) {return c.isEmpty();}
    }
    public boolean contains(Object o) {
        synchronized (mutex) {return c.contains(o);}
    }
}

Released under the MIT License.