LinkedList层次结构
如图所示,LinkedList继承了AbstractSequentialList,实现了List, Deque, Cloneable, java.io.Serializable接口。
AbstractSequentialList:
这是一个抽象类,继承了AbstractList类。而AbstractList类实现了List接口,所以AbstractSequentialList类主要作用是实现了List接口的大量实现,让LinkedList最大限度的减少不必要的实现的工作量。同时AbstractSequentialList有一个抽象方法:
public abstract ListIterator<E> listIterator(int index);
需要LinkedList将该抽象方法具体化。
List接口:
因为AbstractList已经实现了List接口,LinkedList又实现了一遍,通过查阅资料得知这里是作者加上的,作者感觉会有用,但是实际没有影响。具体的网址:http://stackoverflow.com/questions/2165204/why-does-linkedhashsete-extend-hashsete-and-implement-sete(参考博客:https://www.cnblogs.com/zhangyinhua/p/7687377.html)。
Deque接口:
Deque接口是双端队列的称呼。实现了Deque接口,LinkedList就变成了双端链表,可以实现在头部或者尾部插入或者删除元素。
LinkedList属性
链表容量:
transient int size = 0;
链表的头节点:
transient Node<E> first;
链表的尾节点:
transient Node<E> last;
LinkedList构造方法
无参构造函数:
/**
* Constructs an empty list.
*/
public LinkedList() {
}
带参构造函数:
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
带参构造函数会构造一个包含参数集合的链表。
LinkedList常用的方法
add(E e)方法:
向链表尾部添加一个元素e
代码:
public boolean add(E e) {
linkLast(e);
return true;
}
add()方法调用 linkLast(e)方法:
/**
* Links e as last element.
*/
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++;
}
linkLast(e)方法将元素e添加在链表的尾部,节点变量l保存当前节点的尾部节点,构造一个新的节点Node,构造函数Node<>(l, e, null)表示:l为节点e的前一个节点,e为当前节点,第三个参数表示新添加节点的下一个节点。因为是向尾部添加,所以新加节点的下一个节点(next)是null。 last = newNode;表示将链表的尾指针指向新构造的节点。如果l(尾节点)是空的,说明这个链表是空的,那么就将头节点指针也指向新加的节点。如果不是空链表,那么就想原来链表的尾指针,指向新的节点。然后链表容量加一,modCount表示对链表结构的修改次数。回到add(E e)方法,添加元素成功,返回true。
addLast(E e)方法:
像链表尾部添加一个元素e。和add(E e)方法一样,调用linkLast(e)方法。
public void addLast(E e) {
linkLast(e);
}
addFirst(E e)方法:
向链表的头部添加新元素。
public void addFirst(E e) {
linkFirst(e);
}
addFirst调用linkFirst方法。
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++;
}
节点f存储链表的头节点,构造新的节点newNode ,因为实在头部添加,所以构造函数的第一个参数是null,第三个参数表示新节点newNode 的下一个节点指针指向链表的原来的头节点。将链表的头指针指向新节点newNode。如果f(存储原链表的头节点)是空的,说明链表是空的,那么就将链表的尾指针也指向新加的节点newNode,否则将原来链表的头节点的前一元素指针指向新的节点newNode。链表容量加一,链表结构的修改次数加一。
remove(Object o)方法:
从该列表中删除指定元素的第一个匹配项(如果存在)。 如果此列表不包含该元素,则不会更改。
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;
}
o表示要删除的元素,如果o是null。遍历链表,如果匹配到节点元素和o相同,调用unlink方法。
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;
}
x为要删除的节点,element 表示x的存储的节点,next 表示x的后一节点,prev 表示x的前一节点。如果prev == null,即x为头节点,那么链表的头指针,就指向要删除节点的下一节点,即下一节点作为头节点。如果prev 不是null,即要删除的元素不是第一个,那么就将x的前一节点的next指针,指向要删除的节点的x的下一节点,即让x前节点和x的后一节点链接上,将x的前驱置为null。如果 next==null,即删除的是链表尾节点,这链表的尾指针last指向x(要删除的节点)的前驱。如果next 不是null,则让x的后驱节点的前驱指向x的前驱节点,即让x的后驱和x的前驱链接起来,然后将x的后驱置空。将x的元素置空,方便于垃圾回收。链表容量减一,记录修改链表结构的变量modCount加一。返回删除的元素。
返回到remove(Object o)方法,删除成功返回true。如果删除的不是null元素,同样遍历一遍链表,找到先匹配的元素后,调用unlink方法。
removeFirst()方法
删除并返回链表的第一个元素。
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
如果链表是空链表,抛出异常。否则调用unlinkFirst方法。
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;
}
将头节点f的元素置空,便于垃圾回收。f的后驱置空。使之与链表断开连接。然后让链表的头指针指向f的后驱节点。如果f的后驱节点是空的,即链表只有一个节点,那么让链表的尾节点置空,否则,就将f的后驱的前驱置空,即让f的后驱变为头节点。容量减一,修改链表结构的变量加一。返回被删除的元素。
listIterator(int index)方法:
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
返回链表的迭代器,这个迭代器是快速失败的,也就是说如果在创建Iterator之后的任何时候对列表进行结构修改,除了通过list-iterator自己的remove或addmethods之外,list-iterator将抛出ConcurrentModificationException异常。
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
检查索引的范围,如果超出链表范围,抛出异常。
如过没有超出范围,则返回迭代器。
其他方法
因为继承了队列,所以LinkedList还有pop(),push(E e)方法,可以作为栈使用。
offer()和add()区别:add()向队列添加元素会检查队列容量,如果容量不够这抛出异常,offer的方法向队列添加元素,如果容量不够,会直接返回false。