List接口与实现类

List接口
- 特点:有序,有索引,可重复
- 常用方法:https://docs.oracle.com/javase/8/docs/api/
List是一个接口,继承于Collection的接口,代表着有序的队列。
ArrayList和LinkedList
作为List最常用的实现
-
ArrayList基于动态数组实现,存在容量限制,当元素超过最大容量时,会自动扩容。LinkedList基于双向链表实现,不存在容量限制。 -
ArrayList随机访问速度较快,随机插入、删除速度较慢。LinkedList随机插入、删除速度较快,随机访问速度较慢。 -
ArrayList和LinkedList都不是线程安全的。
Vector 和 Stack
Vector 和 Stack 的设计目标是作为线程安全的 List 实现,替代 ArrayList。
-
Vector-Vector和ArrayList类似,也实现了List接口。但是,Vector中的主要方法都是synchronized方法,即通过互斥同步方式保证操作的线程安全。 -
Stack-Stack也是一个同步容器,它的方法也用synchronized进行了同步,它实际上是继承于Vector类。
1. ArrayList
1.1 要点
ArrayList 是一个数组队列,相当于动态数组。ArrayList 默认初始容量大小为 10 ,添加元素时,如果发现容量已满,会自动扩容为原始大小的 1.5倍。应该尽量在初始化 ArrayList 时,为其指定合适的初始化容量大小,减少扩容操作产生的性能开销。
ArrayList源码定义:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable从 ArrayList 的定义,不难看出 ArrayList 的一些基本特性:
-
ArrayList实现了 List 接口,并继承了AbstractList,它支持所有 List 的操作。 -
ArrayList实现了RandomAccess接口,支持随机访问。RandomAccess是 Java 中用来被 List 实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。 -
ArrayList实现了Cloneable接口,支持深拷贝。 -
ArrayList实现了Serializable接口,支持序列化,能通过序列化方式传输。 -
ArrayList是非线程安全的。
1.2 原理
ArrayList 的数据结构
ArrayList 包含了两个重要的元素:elementData 和 size。
transient Object[] elementData;
private int size;-
size- 是动态数组的实际大小。默认初始容量大小为 10 (可以在构造方法中指定初始大小),添加元素时如果发现容量已满,会自动扩容,如果实际大小为偶数就是1.5倍,否则是1.5倍左右! 奇偶不同,比如 :10+10/2 = 15, 33+33/2=49。如果是奇数的话会丢掉小数. -
elementData- 是一个Object数组,用于保存添加到ArrayList中的元素。
ArrayList 的序列化
ArrayList 具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。为此,ArrayList 定制了其序列化方式。具体做法是:
- 存储元素的
Object数组(即elementData)使用transient修饰,使得它可以被 Java 序列化所忽略。 -
ArrayList 重写了writeObject()和readObject()来控制序列化数组中有元素填充那部分内容。
ArrayList 的访问元素
// 获取第 index 个元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}实现非常简单,其实就是通过数组下标访问数组元素,其时间复杂度为 O(1),所以很快。
ArrayList 的添加元素
ArrayList 添加元素时,如果发现容量已满,会自动扩容为原始大小的 1.5 倍。ArrayList 添加元素的实现主要基于以下关键性源码:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return 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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}·ArrayList执行添加元素动作(add 方法)时,调用 ensureCapacityInternal() 方法来保证容量足够。
- 如果容量足够时,将数据作为数组中
size+1位置上的元素写入,并将size自增 1。 - 如果容量不够时,需要使用
grow()方法进行扩容数组,新容量的大小为oldCapacity + (oldCapacity >> 1),也就是旧容量的1.5倍。扩容操作实际上是调用Arrays.copyOf()把原数组拷贝为一个新数组,因此最好在创建ArrayList对象时就指定大概的容量大小,减少扩容操作的次数。
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;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}ArrayList执行删除元素(remove 方法)作时,实际上是调用 System.arraycopy() 将 index+1 后面的元素都复制到index位置上,复制的代价很高。
ArrayList 的 Fail-Fast
modCount 用来记录 ArrayList结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
在AbstractList源码中,有关于其的解释:
/**
* The number of times this list has been <i>structurally modified</i>.
* Structural modifications are those that change the size of the
* list, or otherwise perturb it in such a fashion that iterations in
* progress may yield incorrect results.
* 这个列表在结构上被修改的次数。
* 结构修改是指改变列表的大小,或者以一种可能产生不正确结果的迭代方式扰乱它。
*
* <p>This field is used by the iterator and list iterator implementation
* returned by the {@code iterator} and {@code listIterator} methods.
* If the value of this field changes unexpectedly, the iterator (or list
* iterator) will throw a {@code ConcurrentModificationException} in
* response to the {@code next}, {@code remove}, {@code previous},
* {@code set} or {@code add} operations. This provides
* <i>fail-fast</i> behavior, rather than non-deterministic behavior in
* the face of concurrent modification during iteration.
* 该字段用于iterator和listIterator方法返回的iterator和list iterator实现数据迭代。
* 如果该字段出现了意外的改变,iterator/list iterator在执行next、remove、previous、
* set、add等操作时将抛出ConcurrentModificationException。 这样就提供了快速失败行为,
* 而不会出现在执行迭代过程中的不确定行为。
*
* <p><b>Use of this field by subclasses is optional.</b> If a subclass
* wishes to provide fail-fast iterators (and list iterators), then it
* merely has to increment this field in its {@code add(int, E)} and
* {@code remove(int)} methods (and any other methods that it overrides
* that result in structural modifications to the list). A single call to
* {@code add(int, E)} or {@code remove(int)} must add no more than
* one to this field, or the iterators (and list iterators) will throw
* bogus {@code ConcurrentModificationExceptions}. If an implementation
* does not wish to provide fail-fast iterators, this field may be
* ignored.
* 子类是否使用该字段是可选的。 如果子类想要提供快速失败的iterator/list iterator,
* 只需要在其add、remove或者其他重载的会导致list结构改变的方法中增加该字段的值。
* 单次对add或者remove的调用对该字段值的增加不能超过1,否则iterator/list iterator
* 将抛出虚假的ConcurrentModificationExceptions。 如果实现类不想提供快速失败的迭代器,
* 可以忽略掉该字段。
*/
protected transient int modCount = 0;在进行序列化或者迭代等操作时,需要比较操作前后modCount是否改变,如果改变了需要抛出 ConcurrentModificationException。
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();
}
}2. LinkedList
2.1 要点
LinkedList 基于双向链表实现。由于是双向链表,所以顺序访问会非常高效,而随机访问效率比较低。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable从LinkedList的定义,可以得出LinkedList 的一些基本特性:
-
LinkedList实现了List接口,并继承了AbstractSequentialList,它支持所有List的操作。
-LinkedList实现了Deque接口,也可以被当作队列(Queue)或双端队列(Deque)进行操作,此外,也可以用来实现栈。 -
LinkedList实现了Cloneable接口,支持深拷贝。 -
LinkedList实现了Serializable接口,支持序列化。 -
LinkedList是非线程安全的。
2.2 原理
LinkedList 的数据结构
LinkedList内部维护了一个双链表。LinkedList 通过Node类型的头尾指针(first 和last)来访问数据。
// 链表长度
transient int size = 0;
// 链表头节点
transient Node<E> first;
// 链表尾节点
transient Node<E> last;-
size- 表示双链表中节点的个数,初始为 0。 -
first和last- 分别是双链表的头节点和尾节点。
Node 是LinkedList的内部类,它表示链表中的元素实例。Node 中包含三个元素:
-
prev是该节点的上一个节点; -
next是该节点的下一个节点; -
item是该节点所包含的值。
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
...
}LinkedList 的序列化
LinkedList 与 ArrayList 一样也定制了自身的序列化方式。具体做法是:
- 将
size(双链表容量大小)、first和last(双链表的头尾节点)修饰为transient,使得它们可以被 Java 序列化所忽略。 - 重写了
writeObject()和readObject()来控制序列化时,只处理双链表中能被头节点链式引用的节点元素
LinkedList 的访问元素
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;
}
}获取 LinkedList 第 index个元素的算法是:
- 判断
index在链表前半部分,还是后半部分。 - 如果是前半部分,从头节点开始查找;如果是后半部分,从尾结点开始查找。
显然,LinkedList 这种顺序访问元素的方式比 ArrayList随机访问元素要慢
LinkedList 的添加元素
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
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;
}
}算法如下:
- 将新添加的数据包装为
Node; - 如果尾指针为
null,将头指针指向新节点; - 如果尾指针不为
null,将新节点作为尾指针的后继节点; - 将尾指针指向新节点;
LinkedList 的删除元素.
public boolean remove(Object o) {
if (o == null) {
// 遍历找到要删除的元素节点
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
// 遍历找到要删除的元素节点
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}算法思路如下:
- 遍历找到要删除的元素节点,然后调用
unlink方法删除节点; -
unlink删除节点的方法: - 如果当前节点有前驱节点,则让前驱节点指向当前节点的下一个节点;否则,让双链表头指针指向下一个节点。
- 如果当前节点有后继节点,则让后继节点指向当前节点的前一个节点;否则,让双链表尾指针指向上一个节点
















