目录
LinkedList
继承关系
- LinkedList 实现 Deque (继承自Queue接口)接口,即能将LinkedList当作双端队列使用。存储元素过程中,无需像 ArrayList 那样进行扩容。但有得必有失,LinkedList 存储元素的节点需要额外的空间存储前驱和后继的引用。LinkedList 在链表头部和尾部插入效率比较高,但在指定位置进行插入时,效率一般(此操作的时间复杂度为O(N))。
- LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆,这里是浅复制。
- Serializable接口表示可序列化
- LinkedList相对于ArrayList来说,是可以快速添加,删除元素,ArrayList(非尾部添加)添加删除元素的话需移动数组元素,可能还需要考虑到扩容数组长度
// 可以用此类替换Stack
Queue queue = new LinkedList<>();
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable
add方法
- 新增的元素放置链表的最后面,然后链表的长度(size)加1,修改的次数(modCount)加1
/** 在链表尾部插入元素 */
public boolean add(E e) {
linkLast(e);
return true;
}
/** 在链表指定位置插入元素 */
public void add(int index, E element) {
checkPositionIndex(index);
// 判断 index 是不是链表尾部位置,如果是,直接将元素节点插入链表尾部即可
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/** 将元素节点插入到链表尾部 */
void linkLast(E e) {
final Node l = last;
// 创建节点,并指定节点前驱为链表尾节点 last,后继引用为空
final Node newNode = new Node<>(l, e, null);
// 将 last 引用指向新节点
last = newNode;
// 判断尾节点是否为空,为空表示当前链表还没有节点
if (l == null)
first = newNode;
else
l.next = newNode; // 让原尾节点后继引用 next 指向新的尾节点
size++;
modCount++;
}
/** 将元素节点插入到 succ 之前的位置 */
void linkBefore(E e, Node succ) {
// assert succ != null;
final Node pred = succ.prev;
// 1. 初始化节点,并指明前驱和后继节点
final Node newNode = new Node<>(pred, e, succ);
// 2. 将 succ 节点前驱引用 prev 指向新节点
succ.prev = newNode;
// 判断尾节点是否为空,为空表示当前链表还没有节点
if (pred == null)
first = newNode;
else
pred.next = newNode; // 3. succ 节点前驱的后继引用指向新节点
size++;
modCount++;
}
get方法
- LinkedList 查找过程要稍麻烦一些,需要从链表头结点(或尾节点)向后查找,时间复杂度为 O(N)
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node node(int index) {
// 根据索引位置,决定是前向遍历,还是后向遍历
if (index < (size >> 1)) {
Node x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
CopyOnWriteArrayList
简介
- 适合只读操作远多于可变操作,多线程安全。通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大,这样做的目的是读写分离,读取的时候不会改变读取的内容,写入完成后下次读取就能读取到新内容,所以读不用加锁。
- 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突(因为读的是快照指向原数组,写的时候是copy然后指向新数组)。在构造迭代器时,迭代器依赖于不变的数组快照。
- CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先将原有数组的元素拷贝到新数组中,然后在新的数组中做写操作,写完之后,再将原来的数组引用(volatile 修饰的数组引用)指向新数组。CopyOnWriteArrayList的整个add操作都是在锁的保护下进行的
迭代
- snapshot的目的是,当同时有线程读和写时,写的操作会复制数组A到新数组B,写入,然后将之前的数组A指向新数组B(虚拟机层面阻塞的,而且速度非常快),迭代的snapshot是指向旧数组A(读取和写入同时,读取时写入未完成所以读取的是A)。
public Iterator iterator() {
return new COWIterator(getArray(), 0);
}
static final class COWIterator implements ListIterator {
private final Object[] snapshot;
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
public boolean hasPrevious() {
return cursor > 0;
}
// 省略部分代码
// 对于remove(), set(), add()等操作,COWIterator都会抛出异常
}
get操作
public E get(int index) {
return get(getArray(), index);
}
// volatile
final Object[] getArray() {
return array;
}
private E get(Object[] a, int index) {
return (E) a[index];
}
add操作
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// lock
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 新建一个数组newElements,并将原始数据拷贝到newElements中, newElements数组的长度=“原始数组的长度”+1
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
// 将”volatile数组“引用指向newElements数组,这样旧数组就被GC回收了
setArray(newElements);
return true;
} finally {
// unlock
lock.unlock();
}
}
remove
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
// 删除最后个元素
setArray(Arrays.copyOf(elements, len - 1));
else {
// 复制数组的时候,不复制要删除的数组
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
创建
// 空的
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
// 直接copy Arrays.copyOf底层System.arraycopy,对数组而已深拷贝,但是对元素而言浅拷贝
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
Collections.sort
- 一般做法是List XX中实现Comparable
// 参数接收list
public static > void sort(List list) {
list.sort(null);
}
// ArrayList 和COW list都是调用Arrays.sort, 最终大概率是调用折半插入排序,最坏时O(n平方)
public static void sort(T[] a, int fromIndex, int toIndex,
Comparator super T> c) {
if (c == null) {
sort(a, fromIndex, toIndex);
} else {
// 省略
}
}
参考文章
- LinkedList源码分析(基于JDK8)
- LinkedList 源码分析(JDK 1.8)
- CopyOnWriteArrayList源码解析——JDK1.8
- JAVA的copyonwriteArrayList为什么在写入的时候要重新复制一下数组?