public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
- 实现了List接口,代表着可以实现精准控制,即所谓的按照index进行一系列操作。
- Deque接口,代表底层是双端队列
- Cloneable,Serializable 拷贝和序列化
- 不支持随机访问
基本组成
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
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;
}
}
头节点要么是null,要么头结点的前向节点是null;尾节点要么是null,要么尾节点的后向节点是null
可以看到双向链表的基本组成单元就是Node。节点的一些定义不再赘述,无非就是值和只想前后节点的指针。同时LinkedList不存在容量的问题,所以构造函数相比ArrayList少了指定容量的方式,其他基本一致。
添加元素
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
因为是在头部插入元素,所以构造的新节点的next指针指向当前的头节点,如果原来头节点是空(链表中没有元素),将链表的尾节点也设为该新节点;否则的话,将原头节点的前向节点指向新节点
添加元素的方法有好几种,用的时候注意添加的顺序,可能是添加到头部或者尾部,建议使用名字较为明显的这种方式,添加到头部的还有push。
添加到尾部的方法有add,offer,addLast,当然对应的实现方法中都调用了linkLast
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++;
}
类似插到头节点。新建新节点并且前驱指针指向当前last节点,若last节点是null,说明是空链表,此时新节点也是头节点。否则的话,将原last节点指向新节点。
删除元素
与添加节点类似,删除元素也分为删除头部元素和删除尾部元素,remove/removeFirst/poll/pop 都是删除头部元素,只有removeLast是删除尾部元素
就挑其中一种简要分析下
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
根据节点构造出元素,最后要返回这个元素。
因为要删除的是头节点,所以将该节点值置为null,next指针也置为null,方便gc回收,如果下个节点是空,说明变成了空链表,将尾节点置为null,否则的话,将下个节点的prev置为null。
序列化
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size
s.writeInt(size);
// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
在前边的ArrayList中数组是transient,我们提到了这样防止使用java默认的序列化,因为ArrayList是动态扩容的,数组中并不一定被填满,所以采用了自己写的write/readObject 继续序列化,这样效率更高,这里的LinkedList类似,first和last节点也是transient。
总结
LinkedList底层使用双向链表实现,元素间内存地址不连续,只能使用指针定位,所以便利的时候不要用for,最好用foreach或者迭代器,不然每次都是从头节点开始去找。另外相比ArrayList,LinkedList添加删除的空间代价较小,毕竟只需要指针动一下,不用大费周章。
关注公众号: 程序员二狗