java集合-List篇

JDK提供的集合类型主要分为四种类型:

  1. List:支持重复元素
  2. Set:不支持重复元素
  3. Map:键/值对的映射集
  4. Queue/Deque(double ended queue):queue是在集合尾部添加元素,在头部删除元素的队列,deque是可在头部和尾部添加或者删除元素的双端队列,deque既可以实现队列又可以实现栈

本文基于JDK8,java version “1.8.0_251”

ArrayList

基于数组,支持随机访问,非线程安全

  1. 实现了RandomAccess接口,支持随机访问,查询元素更快,增加元素(非末尾添加)和删除元素比较慢,因为需要移动后面的元素位置。
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

/**
 * Returns the element at the specified position in this list.
 *
 * @param  index index of the element to return
 * @return the element at the specified position in this list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}
  1. 底层是数组默认容量10最大容量Integer.MAX_VALUE-8(2^31 - 8)
/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData; // non-private to simplify nested class access
/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;	    
/**
 * The maximum size of array to allocate.
 * Some VMs reserve some header words in an array.
 * Attempts to allocate larger arrays may result in
 * OutOfMemoryError: Requested array size exceeds VM limit
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  1. 懒加载,使用空参构造方法初始完后,数组容量为0而不是默认容量,添加元素后会扩容至默认容量
/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
  1. 支持fail-fast机制,继承了AbstactList抽象类,继承自AbstactList的类每次调用新增或者删除等修改方法,属性modCount都会自增,当通过Interactor遍历集合时,只要modCount被其他线程修改,就会抛出ConcurrentModificationException
public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}
  1. 非线程安全,可以通过**Collections.synchronizedList()**方法把它转成线程安全的集合,不过这样性能不是很高
  2. 扩容机制:扩容为原来的1.5倍
/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
  1. 相比LinkedList空间占用更小

LinkedList

基于双向链表,增加和删除元素快,非线程安全

  1. 实现了Deque接口,表明它是双向链表结构的,没有实现RandomAccess接口,所以不支持随机访问。增加和删除快,查询和修改元素慢。
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

/**
 * Returns the element at the specified position in this list.
 *
 * @param index index of the element to return
 * @return the element at the specified position in this list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}
/**
 * Returns the (non-null) Node at the specified element index.
 */
Node<E> node(int index) {
    // assert isElementIndex(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;
    }
}
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;
    }
}
  1. 可以用来实现栈这个数据结构,或者直接把它当成栈来使用
  2. 支持fail-fast机制,继承了AbstractSequentialList抽象类,而它又继承了AbstactList抽象类,所以每次调用新增或者删除等修改方法,属性modCount都会自增,当通过Interactor遍历集合时,只要modCount被其他线程修改,就会抛出ConcurrentModificationException
  3. 非线程安全,可以通过**Collections.synchronizedList()**方法把它转成线程安全的集合,不过这样性能不是很高
  4. 由于每个元素包含next和prov指针,所以相比ArrayList空间占用更大

Vector

和ArrayList非常相似,主要不同:Vector是线程安全,ArrayList非线程安全。

  1. 实现了RandomAccess接口,支持随机访问,查询元素更快,增加元素(非末尾添加)和删除元素比较慢,因为需要移动后面的元素位置。
public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  1. 底层是数组默认容量10最大容量也是Integer.MAX_VALUE-8(没有显式定义这点,但是jvm规定数组的最大长度为Integer.MAX_VALUE-8)
  2. 无懒加载,使用空参构造初始化对象后,数组长度为10。
/**
 * Constructs an empty vector so that its internal data array
 * has size {@code 10} and its standard capacity increment is
 * zero.
 */
public Vector() {
    this(10);
}
  1. 支持fail-fast机制,继承了AbstactList抽象类,继承自AbstactList的类每次调用新增或者删除等修改方法,属性modCount都会自增,当通过Interactor遍历集合时,只要modCount被其他线程修改,就会抛出ConcurrentModificationException
public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  1. 线程安全,方法使用了synchronized修饰保证线程安全,但是效率较低,不推荐使用。
  2. 扩容机制:扩容为原来的2倍
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

Stack

先进后出,线程安全,效率较低

  1. 线程安全,继承至Vector,相当于使用Vector的方法实现的栈,但效率较低,建议使用ConcurrentLinkedDeque代替

CopyOnWriteArrayList

基于数组,随机访问,线程安全,写时复制,读写分离,内存占用大,弱一致性,适合多读场景

  1. 实现了RandomAccess接口,支持随机访问,查询元素更快,增加元素(非末尾添加)和删除元素比较慢,因为需要移动后面的元素位置。
public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
  1. 底层是数组默认容量0最大容量Integer.MAX_VALUE-8(没有显式定义这点,但是jvm规定数组的最大长度为Integer.MAX_VALUE-8)
  2. 无懒加载,默认容量就是0
/**
 * Creates an empty list.
 */
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}
  1. 不支持fail-fast机制,没有继承于AbstractList,仅实现了List接口,Iterator实现类中,没有checkForComodification(),更不会抛出ConcurrentModificationException异常!
public E next() {
    if (! hasNext())
        throw new NoSuchElementException();
    return (E) snapshot[cursor++];
}
  1. 线程安全。通过volatile和互斥锁来实现。增加、删除、修改操作使用ReenTrantLock实现同时只有一个线程能够获取锁。增加、删除、修改操作都是先复制一个新数组,然后对新操作进行操作,操作完之后再将新数组设置为容器,这种策略叫做写时复制,缺点就是内存占用大。但是在写入新数组后,将新数组设置为容器之前,执行查询操作,查询的是旧值,这就是写时复制策略产生的弱一致性问题
/**
 * {@inheritDoc}
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E get(int index) {
    return get(getArray(), index);
}

/**
 * Replaces the element at the specified position in this list with the
 * specified element.
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        E oldValue = get(elements, index);

        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element;
            setArray(newElements);
        } else {
            // Not quite a no-op; ensures volatile write semantics
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}
  1. 无扩容机制,由于每次增加元素都是重新创建新的数组,并且新数组容量为旧数组容量+1
/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
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();
    }
}

问题

为什么java.util.concurrent包里没有并发的ArrayList实现

回答:很难去开发一个通用并且没有并发瓶颈的线程安全的List,像ConcurrentHashMap这样的类的真正价值并不是它们保证了线程安全。而在于它们在保证线程安全的同时不存在并发瓶颈。

问题在于,像“Array List”这样的数据结构,你不知道如何去规避并发的瓶颈。拿contains() 这样一个操作来说,当你进行搜索的时候如何避免锁住整个list?

另一方面,Queue 和Deque (基于Linked List)有并发的实现是因为他们的接口相比List的接口有更多的限制,这些限制使得实现并发成为可能。

CopyOnWriteArrayList是一个有趣的例子,它规避了只读操作(如get/contains)并发的瓶颈,但是它为了做到这点,在修改操作中做了很多工作和修改可见性规则。 此外,修改操作还会锁住整个List,因此这也是一个并发瓶颈。所以从理论上来说,CopyOnWriteArrayList并不算是一个通用的并发List。