1. LinkedList
LinkedList内部结构是双向链表,继承 AbstractSequentialList 类(继承自 AbstractList),除了实现List接口外还实现了Deque队列接口。它线程不安全。
LinkedList成员变量以及Node节点的数据结构如下(阅读其源码可知,节点中的item元素可以为null):
public class LinkedList<E> ... {
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
}
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
}
它的UML类图如下:
源码:双向链表,感觉没啥好说的……
接下来讲下ArrayList吧
2. ArrayList
工作中用的最多的List。ArrayList 同样继承了AbstractSequentialList 类,本质上是一个数组,所以它支持随机访问。set/get时效率优于LinkedList;add/remove时输于LinkedList,因为ArrayList可能扩容、移位等。(数组与链表的区别,没啥好说的)它线程不安全。
ArrayList成员变量如下:
public class ArrayList ... {
private static final int DEFAULT_CAPACITY = 10;
// 用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
// 用于默认大小的空实例的共享空数组实例。我们将其与空元素数据区分开来,以便了解添加第一个元素时要膨胀多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储ArrayList元素的数组缓冲区。
transient Object[] elementData;
private int size;
}
它的UML图如下:
ArrayList的扩容操作:
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);
}
不难看出,ArrayList每次扩容时扩容capacity数组容量的1.5倍。
3. Vector
Vector,向量也,底层也是数组,初始化容量与ArrayList都默认为10,同样支持随机访问,不同的是它是线程安全的,那为啥它是线程安全的呢?那是因为它主要的方法加了 synchronized 关键字(这种锁粒度太粗了,况且Vector还有数组扩容的耗时操作,所以不推荐为了线程安全性而使用Vector)。
它的UML图如下:
它的成员变量如下:
// 保存的数组
protected Object[] elementData;
// 元素个数
protected int elementCount;
// 容量自增的量。如果容量增量小于或等于零,则每次需要增长时,向量的容量都会翻倍。
protected int capacityIncrement;
// 要分配的最大数组大小。由于虚拟机在数组中保留一些头文字,尝试分配更大的数组可能会导致OOM:请求的数组大小超过VM限制
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
构造函数如下:
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
扩容函数如下:
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);
}
如代码所说,Vector 每次扩容时都会增加capacityIncrement大小,不够则将数组扩大一倍(容易越界或造成空间浪费)。如果 新数组长度 < 原数组长度,不够的位置将被截断;相反的,多余的位置将被填充null。Vector扩容时,使用到 Arrays.copyOf() 数组复制操作,频繁扩容比较耗时。
4. Stack
Stack继承Vector,底层也是数组,使用数组尾部模拟栈顶。empty、peek、pop、push、search没啥好说的。
5. 总结
LinkedList与ArrayList比较
链表与数组的区别。写多,用LinkedList;读多,用ArrayList。
Vector与ArrayList比较
对于扩容操作,ArrayList的扩容逻辑相对于数组容量是动态的,这保证了在扩容大小与扩容频率之间的平衡。