前言
List接口在Collection的基础上添加了大量的方法,使得可以在List的中间插入和删除元素,并且存放的顺序与插入的顺序一致。
List接口
public interface List<E> extends Collection<E> {
// 查询操作
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
// 修改操作
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
// 基于位置的访问操作
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
// 搜寻操作
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);
……
}
常用实现类
List
|------ArrayList
|------Vector
| |-------Stack
|------LinkedList
ArrayList
ArrayList 是 List接口的一个实现类,可以说 ArrayList 是我们使用最多的 List 集合。从类名可以看出,ArrayList应该是基于数组而生成的,而事实确实如此,ArrayList的底层实现是基于动态数组而完成,保留了数组在存取元素方面的优势。
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
可以看出,ArrayList的默认容量为10,内部存储继承自Object的对象数组,如果使用无参构造函数时,使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA;如果为含参构造函数,使用EMPTY_ELEMENTDATA。elementData数组用来存储集合中的内容,数组的长度即为ArrayList的Capacity,有别于size属性,size表示当前ArrayList中保存的元素个数,数组的最大值为Integer.MAX_VALUE - 8。
LinkedList
LinkedList是双向链表,链表中的每个节点都包含了对前一个和后一个元素的引用。节点的数据结构如下:
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;
}
}
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
……
}
Vector和Stack
Vector的数据结构和ArrayList差不多,包含了3个成员变量:elementData,elementCount,capacityIncrement。
protected Object[] elementData;
protected int elementCount;
protected int capacityIncrement;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
public Vector() {
this(10);
}
elementData是Object[]的数组,初始大小为10,会不断的增长;elementCount是元素的个数;capacityIncrement是动态数组增长的系数。Stack继承至Vector,提供对数组末尾元素的新增、读取和删除操作。Vector与ArrayList最大差异在于Vector是线程安全的,而ArrayList不是。
初始化和扩容
ArrayList
ArrayList提供如下三种构造方法:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
其中,使用ArrayList(int initialCapacity)和ArrayList()初始化后,elementData中没有元素,size为0,不过elementData的长度有可能不同。ArrayList(Collection c)将集合c转化为数组,然后检查转化的类型,如果不是Object[]类型,使用Arrays类中的copyOf方法进行复制;同时,如果c中没有元素,使用EMPTY_ELEMENTDATA初始化。
当使用诸如add(E)、add(int, E)、addAll(Collection)、addAll(int, Collection)等方法时存在扩容的可能性。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);
}
简单来讲,扩容后的容量为原先容量的1.5倍,最小为原先值加1,最大为Integer.MAX_VALUE或者Integer.MAX_VALUE - 8。
LinkedList
LinkedList提供如下两种构造方法:
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
默认从链表尾端新增元素,LinkedList由于结构特点,在新增节点时只要修改相关节点引用即可完成,因此并无特定扩容方式,从某种意义上讲,LinkedList无扩容之意义。
Vector和Stack
Vector提供如下四种构造方法:
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector() {
this(10);
}
public Vector(Collection<? extends E> c) {
elementData = c.toArray();
elementCount = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
Stack的构造方法如下:
public Stack() {
}
Vector和Stack本质上都是数组,默认情况下都是在数组尾部新增元素。Vector构造方法接受一个带有扩容增量的参数,用于限定每次扩容的增量。
两者的扩容算法一致:
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);
}
扩容量capacityIncrement小于等于0时,扩容后的容量为原先的2倍,如果大于0,则每次扩容大小为capacityIncrement,最小值最大值参考ArrayList。
关键方法比较
ArrayList、LinkedList、Vector和Stack都实现了List接口,但是重写相关方法时逻辑不完全一样,笔者整理了如下差异:
ArrayList | LinkedList | Vector、Stack | |
---|---|---|---|
add() | 默认情况下尾部新增,如果指定位置新增,则需要移动元素,甚至需要扩容 | 默认情况下尾部新增,如果指定位置新增,则需搜索指定位置完成新增,无需移动原先元素 | 同ArrayList相关处理逻辑 |
remove() | 删除相关元素,同时移动后续元素,并调整capacity | 搜索相关元素,并修改相关元素引用为null,无需移动元素 | 同ArrayList相关处理逻辑 |
get()、set() | 快速定位元素 | 遍历链表搜索 | 同ArrayList相关处理逻辑 |
toArray() | 复制内部数组 | 遍历链表按照节点创建数组 | 同ArrayList相关处理逻辑 |
总体来说,ArrayList、Vector做插入、删除的时候,慢在数组元素的批量copy,快在寻址。同时,ArrayList的操作不是线程安全的!一般在单线程中才使用ArrayList,而在多线程中一般使用Vector或者CopyOnWriteArrayList。LinkedList做插入、删除的时候,慢在寻址,快在只需要改变前后Entry的引用地址。
总结
本文比较详尽叙述了ArrayList、LinkedList和Vector之间的区别,涉及到的多线程安全问题,后续会有讲解,希望对读者有帮助。