ArrayList,Vector,CopyOnWriteArrayList,LinkedList 都实现了List接口,其中LinkedList还实现了 Queue接口,下们我就结合源码来介绍一下他们原理与异同。
概览
数据结构:基于数组实现,支持快速随机访问,实现了RandomAccess接口 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable初始化大小为10
private static final int DEFAULT_CAPACITY = 10;扩容原理
添加元素的时候调用ensureCapacityInternal(size+1)函数确保数组容量足够,如果容量不足,则要调用grow(minCapacity)方法扩容,扩容后的容量为 newCapacity=oldCapacity+(oldCapacity>>1),即为原来的1.5倍,如果1.5倍仍然比需要的少,则扩展为需要的容量,如果容量大于MAX_VALUE,则扩容为MAX_VALUE。
扩容要需要调用Arrays.copyOf()函数进行拷贝,需要调用System.arrayCopy函数,性能代价非常大,所以最好开始就确定容量,减少扩容次数。
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } 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); }删除元素
删除index位置的数据之后需要调用System.arrayCopy函数,将index后面的数据前移一位,所以ArrayList 的删除操作代价非常大。
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }Fail-Fast与Fail-Safe
容器维持了一个modCount变量,当 容器结构发生改变(添加一个元素,删除一个元素,或者对容器容器进行调整)的时候,modCount值就会加1.
当序列化或者进行迭代操作(迭代器)的时候,需要比较modCount操作前和操作后的值,如果操作后的值与操作前的值不一致,就会报ConcurrentModfityException错误。
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }安全失败:Fail-Safe
采用Fail-Safe机制访问的集合容器,在遍历时不是在集合内容上访问的,而是在集合的拷贝上访问的。
由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception,例如CopyOnWriteArrayList。
注意:java.util下的集合容器都是Fail-Fast,java.util.concurrent下的集合容器都是Fail-Safe
序列化
ArrayList保存元素的数组elementData 用transient修饰,表明默认不会被序列化
transient Object[] elementData; // non-private to simplify nested class accessArrayList序列化提供两个函数readObject和writeObject只序列化数组中有数据填充的部分。
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff s.defaultReadObject(); // Read in capacity s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } } private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }同步
它的实现与 ArrayList 类似,但是使用了 synchronized 进行同步。
public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } public synchronized E get(int index) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); return elementData(index); }与ArrayList区别
Vector 是线程安全的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。替代方案
可以使用 Collections.synchronizedList(); 得到一个线程安全的 ArrayList。
List<String> list = new ArrayList<>(); List<String> synList = Collections.synchronizedList(list);也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类。
List<String> list = new CopyOnWriteArrayList<>();读写分离 (性能比Collections.synchronized 强许多)
写入元素的时候是在一个复制的数组上进行,而读操作是在原来的数组上进行,做到了读写分离。
写操作的时候需要加锁,防止多线程并发写入的时候发生数据丢失
写操作结束的时候每次都要将原始数组指向复制数组。
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(); } } final void setArray(Object[] a) { array = a; } @SuppressWarnings("unchecked") private E get(Object[] a, int index) { return (E) a[index]; }适用场景
适用于由于读写分离,大大提升了度的性能,因此读多写少的场景。
缺点:
内存开销:因为每次都要一个复制的数组,所以导致内存翻倍。
数据不一致:每次都要复制的最后原始数组才指向新的数组,在这期间,是读取不到新的数据的,所以有数据访问不一致的风险。
所以CopyOnWriteArrayList不适合内存敏感及实时性很高的场景。
概览
数据结构:LinkedList实现了List和Queue接口,它基于双向链表实现,使用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; } }内部情况: 他存储了一个first 和last指针
transient Node<E> first; transient Node<E> last;与ArrayList比较
ArrayList基于动态数组实现,LinkedList基于双向链表实现。ArrayList支持元素随机访问,LinkedList不支持随机访问。LinkedList在任意位置添加或者删除元素更快。