本文主要是分析实现List接口的ArrayList、LinkedList、Vector、Stack的源码。
1. ArrayList
ArrayList是使用Object数组保存数据的,并且加上了transient关键字,所以在ArrayList序列化时,elementData不会直接被序列化
transient Object[] elementData;
ArrayList有3种构造函数:第1种是无参的,这时候
elementData是一个长度为0的空数组, 当往List里添加元素时才会为elementData分配空间;第2种是传入
initialCapacity
的参数,会为
elementData分配
initialCapacity的空间;第3种是传入List,此时会用
Arrays.copyOf拷贝传入list中的数据给elementData。
ArrayList中数组采用1.5倍的增长方式的,如果增长后空间还不够,则数组长度取当前要插入数据总长度,增长后将原来的数据拷贝到新生成的数组中,代码如下:
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);
}
2.LinkedList
LinkedList是采用链表存储数据的List,有以下三个成员变量,分别表示长度、链表头、链表尾。
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
其中,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;
}
}
LinkedList除了支持List接口的方法外,还支持peek、poll、push、pop等方法,只要对于数据结构有一定了解的同学都能够完成类似代码的编写,因此在此就不做过多的介绍。 另外,在LinkedList的remove和Clear方法中,都有将node的item、next、prev置为null,以便GC回收。
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
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++;
}
3.Vector
vector同ArrayList一样,都是采用数组来存储数据,但vector与ArrayList有两点不同:
(1)Vector在public的方法上使用了 synchronized 关键字来保证同步
(2)Vector在构造时可以传递一个长度的增长量capacityIncrement,如果该值不小于0则每次按该值增长,否则按2倍增长。
4.Stack
Stack继承自Vector,只不过多了peek、push、pop三个方法。
上述的ArrayList、LinkedList、Vector、Stack,ArrayList和LinkedList都是非线程安全的,因此要使用线程安全的List,可以调用Collections.synchronizedList方法,它会将传入的List包装成SynchronizedList,是线程安全的,代码如下:
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
SynchronizedList继承自SynchronizedCollection,它有一个成员变量mutex作为同步的信号量,在实现的List的每一个方法中,采用synchronized关键字获取mutex,然后再调用包装前的list相应方法。
5.CopyOnWriteArrayList
CopyOnWriteArrayList也是采用数组来存储数据的,但是它每次新增数据时都会重新生成一个新的数组,因此CopyOnWriteArrayList适用于读多写少的场景。与上述几个类不同,它位于concurrent包,通过ReentrantLock类型的成员变量lock实现线程安全,在add、remove、retain、set等方法对数据进行操作之前,先调用ReentrantLock的lock()方法获取锁,操作完成之后再调unlock释放锁。如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();
}
}