Java里的LinkedList是基于双向链表来实现的, 众所周知, 链表的增/删性能优秀而读/取较为复杂。
初始化
构造器
public LinkedList(){} //空方法
transient int size = 0; //链表逻辑长度
transient Node first; //链表头
transient Node last; //链表尾
构造方法是空方法,这时LinkedList有size和两个指向链表头、尾的引用。
Node<E>
节点
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是一个内部静态私有类,定义了链表上的元素节点。
增删
增加元素
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = Node<>(last, e, null);
last = newNode;
if (l == null) //原来的last元素为空
first = newNode;
else //原来的last元素不为空
l.next = newNode;
size++;
modCount++;
}
主要注意一个if-else的判断,if (l == null)
为true
时,表示LinkedList中原来的链尾为空,此时由final Node<E> newNode = Node<>(last, e, null);
创建的newNode元素指向前后元素的引用皆为空,所以它应当是第一个元素,要赋值给first。
判断为false
时把新元素添加到原本last元素后既可以。
删除元素
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){
//可以断言x不可能为null
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if(x.prev == null) { //x的前缀为null,即x为链头
first = next; //修改LinkedList链头的引用为x的后缀元素
} else {
prev.next = next; //不是链头就把x的前缀的next指向x的后缀,跳过x
x.prev = null; //把x内的前缀引用置空,因为是双向链表所以要修改前后两个节点的相互引用才能彻底断开链接
}
if (next == null) { //x的后缀为null,即x为链尾
last = prev; //修改LinkedList链尾的引用为x的前缀元素
} else {
next.prev = prev; //不是链头就把x的后缀元素的prev指向x的前缀,跳过x
x.next = null; //把x内的后缀引用置空
}
x.item = null;
size--;
modCount++;
return element;
}
删除方法里使用了equals()方法来比较元素是否相同,所以要注意实际业务中是否要重写equals()方法。
读取、设置
读取元素
public E get(int index) {
checkElementIndex(index); //检查index是否越界并报错
return node(index).item;
}
Node<E> node(int 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 = 0; i < index; i++)
x = x.prev;
return x;
}
}
逻辑比较简单,利用元素的prev和next引用迭代获取目标元素,只是需要根据index来决定迭代的起始位置。
设置元素
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
复杂度
增删
- LinkedList使用add(E e)增加元素时只是修改了链尾元素的引用,显然复杂度为O(1)。
- remove(Object o)方法 省略遍历查找元素 消耗的时间后,实际删除操作也是修改链表中元素的引用,复杂度也为O(1)。而ArrayList 省略遍历查找元素 消耗的时间后,实际删除操作中需要复制底层数组,所以复杂度是O(n)。
- 理论上LinkedList增删优于ArrayList 。
- 为什么要 省略遍历查找元素 消耗的时间?因为某种意义上讲这是读取、定位操作,不属于删除操作本身。
读取
- LinkedList使用
get(int index)
或者set(int index, E element)
来读取、设置元素时需要从链表头、尾来迭代获取,所以复杂度是O(n)。 - ArrayList的读取和设置直接通过底层数组下标实现,所以复杂度是O(1)。
- 理论上ArrayList读取优于LinkedList。