List Interface
List
接口继承自 Collection
, 除了继承了 Collection
中的能力,自身拓展了几个默认方法:
// 批量修改操作
default void replaceAll(UnaryOperator<E> operator)
// 排序,使用的是 Arrays.sort
default void sort(Comparator<? super E> c)
// 位置访问操作
E get(int index);
E set(int index, E element);
void add(int index, E element); // Collection 有 add ,这里拓展了指定 index 。
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
相较于 Collection
拓展的功能:
- 批量操作增加排序,说明
List
是有序的。 - 位置访问操作,说明
List
是可以进行位置索引的。 - 容器支持了子
List
,可以对List
进行截取。
实现
List 接口的实现包括:Vector
、ArrayList
、LinkedList
。
ArrayList
基于动态数组实现,支持随机访问。允许存储包括 null
的元素,除了实现 List
接口外,还提供了一些方法用来处理数组扩容的方法。 它与 Vector
的区别在于,Vector
是同步的,所有操作方法都加了 synchronized
修饰。而 ArrayList
没有。
继承关系
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
复制代码
ArrayList
继承自 AbstractList
, 并实现了 List
、RandomAccess
. Cloneable
和 Serializable
。
AbstractList
对 List
接口的一些方法提供了默认实现,最简单的例子是,List
接口里面有多态的 add
方法,对其进行了包装:
public boolean add(E e) {
add(size(), e);
return true;
}
public void clear() {
removeRange(0, size());
}
复制代码
还对一些索引操作进行了默认实现:
public int indexOf(Object o) {
ListIterator<E> it = listIterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return it.previousIndex();
} else {
while (it.hasNext())
if (o.equals(it.next()))
return it.previousIndex();
}
return -1;
}
复制代码
另外,还实现了内部私有迭代器 Itr
和子列表类型 SubList
。
RandomAccess
用于对支持随机访问的数据结构进行标记。主要作用是,在一些算法中,针对随机访问和顺序访问,会有不同的性能。算法能够识别出这个接口标记的数据结构是随机访问的。
Cloneable
复制能力,实现该接口的类可以通过 Object.clone()
对该类的实例进行逐个字段的复制。
Serializable
序列化能力。
底层实现
transient Object[] elementData;
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);
}
}
复制代码
ArrayList
内部真实存储数据的结构是一个数组。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
复制代码
添加数据是顺序的在数组尾部添加新元素,当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小。
扩容逻辑
Add
方法中,第一个调用的是 ensureCapacityInternal(int)
:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
复制代码
calculateCapacity
方法是用来计算扩容数量的:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
复制代码
当 elementData
为空数组时,初次扩容是返回 10 和 minCapacity
较大一方的值。 否则返回的是 minCapacity
。 而 add
方法中是 size + 1
也就是说,以每次扩容一个进行的。这样始终保持了元素数量 = 数组容量,不会造成内存空间的浪费。
真正的扩容方法在 ensureExplicitCapacity
中:
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 数组修改次数增加
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity); // 真正扩容方法
}
复制代码
内部逻辑也很简单,当数组需要的最小容量 minCapacity
大于 elementData
时,进行扩容,通过真正的扩容方法 grow(int)
来进行:
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
还对外提供了节省内存空间的自动调整容量方法:
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA // EMPTY_ELEMENTDATA = {}
: Arrays.copyOf(elementData, size);
}
}
复制代码
Fail-Fast机制
ArrayList
也采用了快速失败的机制,通过记录 modCount
参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
时间复杂度
因为底层实现是数组,所以它的 size
、isEmpty
、get
、set
的时间复杂度都是 O (1) 级的。
Vector
继承关系
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
复制代码
继承关系和 ArrayList
一模一样。
底层实现
protected Object[] elementData;
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);
}
// ...
复制代码
底层实现也和 ArrayList
相同。
扩容逻辑
扩容逻辑也是从 add
开始,这里有个不同是,add
加了 synchronized
关键字修饰:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
// 和 ArrayList 逻辑相同
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
复制代码
与 ArrayList 的区别
底层实现都基本相同,唯一的区别是 Vector
是线程安全的。
栈结构的实现
Vector
有一个子类 Stack
,也就是栈结构类型。表示对象的后进先出堆栈。Stack
继承自 Vector
,并拓展了五个允许将容器视为栈结构的操作。 包括常见的 pop
和 push
操作、以及查看栈顶元素的方法、检查栈是否为空的方法以及从栈顶向下进行搜索某个元素,并获取该元素在栈内深度的方法。
Deque 接口及其实现提供了一组更完整的 LIFO 堆栈操作能力,应该优先考虑使用 Deque 及其实现。例如:
Deque<Integer> stack = new ArrayDeque<Integer>(); 复制代码
LinkedList
基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。
继承关系
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
复制代码
- AbstractSequentialList:这是一个 List 接口的骨架实现,数据处理逻辑是基于顺序访问的数据结构(例如链表)来实现的。
- Deque:双向队列,支持从两端对元素进行插入和移除。
- Cloneable:对象复制。
- Serializable:序列化能力。
底层实现
transient Node<E> first;
transient Node<E> last;
public LinkedList() {
}
复制代码
通过 first
和 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;
}
}
复制代码
添加操作:
public boolean add(E e) {
linkLast(e);
return true;
}
复制代码
真实的添加操作:
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++;
}
复制代码
删除操作:
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x); // x 的 next 和 prev 置为null
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
复制代码
查找操作:
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;
}
}
复制代码
查找操作的实现是进行了优化的,size >> 1
的值 size / 2 ,如果是在链表的左侧,则从前查找,否则则从尾部向前查找。
时间复杂度
等同于链表结构操作的时间复杂度, O(n) 。
其他能力
因为 LinkedList
实现了 Deque
,所以它可以看作是一个队双向列,也可以当栈结构去用。(Java 已声明不建议使用 Stack 类)。栈或队列,现在的首选是 ArrayDeque
,它有着比 LinkedList
(当作栈或队列使用时)有着更好的性能。
另一方面,它的操作方法并没有进行同步处理,所以不是线程安全的。
LinkedList
基于双向队列的能力,提供了一些获取头节点和尾部节点的方法,这些方法后续在 Queue
接口中详细讲解。这里了解一下基本内容:
boolean offer(E e); // 将元素插入到队列尾部中,返回插入结果
E peek(); // 检索队列头部元素但不删除,如果队列为空,返回 null
void push(E e); // 将元素插入到队列头部 等效于 addFirst
E pop(); // 将队列第一个元素删除,等效于 removeFirst
复制代码
总结
- Vector 和 ArrayList 的实现基本一致,底层数据结构都是数组,最大的区别是 Vector 是线程安全的。
- LinkedList 不是线程安全的,它的底层实现是双向链表。
- LinkedList 不光可以当作双向队列使用,还可以当作栈。