java集合-List篇
JDK提供的集合类型主要分为四种类型:
- List:支持重复元素
- Set:不支持重复元素
- Map:键/值对的映射集
- Queue/Deque(double ended queue):queue是在集合尾部添加元素,在头部删除元素的队列,deque是可在头部和尾部添加或者删除元素的双端队列,deque既可以实现队列又可以实现栈。
本文基于JDK8,java version “1.8.0_251”
ArrayList
基于数组,支持随机访问,非线程安全
- 实现了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);
}
- 底层是数组,默认容量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;
- 懒加载,使用空参构造方法初始完后,数组容量为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;
}
- 支持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();
}
- 非线程安全,可以通过**Collections.synchronizedList()**方法把它转成线程安全的集合,不过这样性能不是很高
- 扩容机制:扩容为原来的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);
}
- 相比LinkedList空间占用更小
LinkedList
基于双向链表,增加和删除元素快,非线程安全
- 实现了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;
}
}
- 可以用来实现栈这个数据结构,或者直接把它当成栈来使用
- 支持fail-fast机制,继承了AbstractSequentialList抽象类,而它又继承了AbstactList抽象类,所以每次调用新增或者删除等修改方法,属性modCount都会自增,当通过Interactor遍历集合时,只要modCount被其他线程修改,就会抛出ConcurrentModificationException
- 非线程安全,可以通过**Collections.synchronizedList()**方法把它转成线程安全的集合,不过这样性能不是很高
- 由于每个元素包含next和prov指针,所以相比ArrayList空间占用更大
Vector
和ArrayList非常相似,主要不同:Vector是线程安全,ArrayList非线程安全。
- 实现了RandomAccess接口,支持随机访问,查询元素更快,增加元素(非末尾添加)和删除元素比较慢,因为需要移动后面的元素位置。
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- 底层是数组,默认容量10,最大容量也是Integer.MAX_VALUE-8(没有显式定义这点,但是jvm规定数组的最大长度为Integer.MAX_VALUE-8)
- 无懒加载,使用空参构造初始化对象后,数组长度为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);
}
- 支持fail-fast机制,继承了AbstactList抽象类,继承自AbstactList的类每次调用新增或者删除等修改方法,属性modCount都会自增,当通过Interactor遍历集合时,只要modCount被其他线程修改,就会抛出ConcurrentModificationException
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- 线程安全,方法使用了synchronized修饰保证线程安全,但是效率较低,不推荐使用。
- 扩容机制:扩容为原来的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
先进后出,线程安全,效率较低
- 线程安全,继承至Vector,相当于使用Vector的方法实现的栈,但效率较低,建议使用ConcurrentLinkedDeque代替
CopyOnWriteArrayList
基于数组,随机访问,线程安全,写时复制,读写分离,内存占用大,弱一致性,适合多读场景
- 实现了RandomAccess接口,支持随机访问,查询元素更快,增加元素(非末尾添加)和删除元素比较慢,因为需要移动后面的元素位置。
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
- 底层是数组,默认容量0,最大容量Integer.MAX_VALUE-8(没有显式定义这点,但是jvm规定数组的最大长度为Integer.MAX_VALUE-8)
- 无懒加载,默认容量就是0
/**
* Creates an empty list.
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
- 不支持fail-fast机制,没有继承于AbstractList,仅实现了List接口,Iterator实现类中,没有checkForComodification(),更不会抛出ConcurrentModificationException异常!
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
- 线程安全。通过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
/**
* 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。