LinkedList
源码剖析
基于jdk1.7
一、继承关系
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
实现了List接口、Deque接口、Cloneable接口和Serializable接口,所以LinkedList可以实现克隆,序列化,可以进行头插尾插、头删尾删等。
二、成员变量和内部类
1.内部类
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这个内部类是LinkedList底层存储元素的双向链表结构的结点实现。
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned = null;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
}
ListItr是LinkedList内部迭代器的实现。ListIterator内部与Iterator相同的方法有hasNext方法、next方法和remove方法,但是ListIterator内部此外还有hasPrevious方法、previous方法和set方法,nextIndex方法、previousIndex方法、add方法,既可以访问后驱结点的值,也可以访问前驱结点的值,而且可以获取下一个结点或者上一个结点的下标,也可以改变当前访问对象的值,也可以给集合添加一个新的值。与Iterator相同,每一次执行方法前都需要检验版本,版本不同就会报ConcurrentModificationException异常。 Iterator只能从前往后遍历,ListIterator可以双向遍历。ListIterator适用List接口下的实现类,Iterator适用于集合所有的实现类 ,ListIterator可以添加,修改元素,获取元素的位置。
2.成员变量
transient int size = 0;//记录当前集合内部的元素个数
transient Node<E> first;//记录双向链表的头结点
transient Node<E> last;//记录双向链表的尾结点
三、构造方法
//无参构造,链表内部全是空的
public LinkedList() {
}
//将一个Collection的实现类转化为LinkedList
public LinkedList(Collection<? extends E> c) {
this();
//Collection的方法都有toArray()方法,先转化为数组,然后再逐一存储
addAll(c);
}
四、常用方法
1.add()方法
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 = new Node<>(l, e, null);//尾插法的前驱结点为last
last = newNode;//更新尾结点为新插入的结点
//如果链表还是空的
if (l == null)
first = newNode;//将first也置为新插入的结点
else//如果不为空
l.next = newNode;//之前的尾结点的后继结点指向插入的结点,已经连上了链表
//存储元素的个数加一
size++;
modCount++;
}
addFirst()方法:
public void addFirst(E e) {
linkFirst(e);//头插法
}
/**
* Links e as first element.
*/
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++;
}
addLast()方法:
public void addLast(E e) {
linkLast(e);//尾插法
}
制定位置插入元素的add()方法:
public void add(int index, E element) {
checkPositionIndex(index);
//如果要插入的位置是size的时候,就直接用尾插法插入即可
if (index == size)
linkLast(element);
else//其他的用linkBefore方法处理
//node(index)方法是找到对应下标位置的node结点
linkBefore(element, node(index));
}
//如果给的要插入的位置不在有效范围之内的话就会抛出下标越界异常
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
/**
* Inserts element e before non-null Node succ.
*/
//在链表中插入元素的方法实现
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//获取要插入位置的前一个节点
final Node<E> pred = succ.prev;
//插入后之前的结点会成为要插入结点的下一个结点
final Node<E> newNode = new Node<>(pred, e, succ);
//先与后继结点连上
succ.prev = newNode;
if (pred == null)//如果要插入的位置的头结点的话,更新头结点的值
first = newNode;
else
//与前驱结点连上
pred.next = newNode;
//更新元素个数
size++;
modCount++;
}
实现的Deque的方法:
offer()方法、offerFirst()方法、offerLast()、push()方法内部的实现其实是上述add相关方法的实现:
public boolean offer(E e) {
return add(e);
}
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
public boolean offerLast(E e) {
addLast(e);
return true;
}
2.remove()方法
//删除对应元素的方法
public boolean remove(Object o) {
//LinkedList允许存储null值,所在在判断相等的时候需要分开进行判断
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
//找到对应的节点,调用unlink方法
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
//找到对应的节点,调用unlink方法
unlink(x);
return true;
}
}
}
//集合中没有对应的值,删除失败
return false;
}
/**
* Unlinks non-null node x.
*/
//将节点从链表中删除的核心实现方法
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;
//如果前驱结点为null,则说明要删除的结点是头结点,先将first置为后继结点
//然后再执行下一个判断的else中,将后继结点的前驱置为前驱结点也就是null,然后将要删除节点与后继结点断开,后继结点成为头结点
if (prev == null) {
first = next;
} else {
//如果前驱结点不为null,将前驱结点的next指向后继结点,将当前结点与前驱结点断开
prev.next = next;
x.prev = null;
}
//如果后继节点为null的话,说明要删除的结点为尾结点,之前已经将前驱结点和当前结点断开了,
//所以这里只需要将尾结点置为前驱结点即可
if (next == null) {
last = prev;
} else {
//既不是头结点也不是尾结点的话,之前已经将前驱结点和当前结点断开了,并且next指向了后继结点
//,这里只需要将后继结点和当前结点断开然后再让后继结点指向前驱结点即可。
next.prev = prev;
x.next = null;
}
//当要删除节点的元素置为null,防止内存泄漏
x.item = null;
//更新元素个数
size--;
modCount++;
return element;
}
删除指定位置的元素remove(int index)方法:
public E remove(int index) {
//如果index在合理范围之内的话,会执行unlink()方法删除对应下标的结点,如果不在合理范围内就会抛出下标越界异常
//范围:index >= 0 && index < size
checkElementIndex(index);
return unlink(node(index));
}
删除头结点的方法removeFirst:
public E removeFirst() {
final Node<E> f = first;
//如果链表还是空的,直接抛出异常
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//前驱结点只会有两个状态:与last都为null的时候,存储的元素不为null的同时前驱结点为null
//集合中有多个值时:将头结点的内容都置为null,然后再将头结点置为后驱结点,再让后驱结点去前驱结点断开
//集合中只有一个值时:头结点和尾结点都会被置为null
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;
//如果在删除时链表中只有一个元素的话,last也会被置为null
if (next == null)
last = null;
else
//存在多个值时会将后继结点去头结点断开
next.prev = null;
//更新元素个数
size--;
modCount++;
return element;
}
删除尾结点的方法removeLast():
public E removeLast() {
final Node<E> l = last;
//链表为空抛出异常
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
//后继结点只会有两个状态:与first都为null的时候,存储的元素不为null的同时后继结点为null
//集合中有多个值时:将尾结点的内容都置为null,然后再将尾结点置为前驱结点,再让前驱结点与尾结点断开
//集合中只有一个值时:头结点和尾结点都会被置为null
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
//更新元素个数
size--;
modCount++;
return element;
}
实现的Deque的方法:
poll()方法、remove()方法、pollFirst()方法、pollLast()方法、pop()方法、removeFirstOccurrence()方法、removeLastOccurrence()方法
//删除头结点,链表为空的时候返回null
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//删除头结点,链表为空的时候抛出异常
public E remove() {
return removeFirst();
}
//删除头结点,与poll方法实现过程一样
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//删除尾结点,链表为空的时候返回null值
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
//删除头结点,链表为空的时候抛出异常
public E pop() {
return removeFirst();
}
//从头结点向后找,找到第一个与传入元素相同的结点删除
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
//从尾结点向前找,找到第一个与传入元素相同的结点删除
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
3.set()方法
更改对应位置的元素的值
public E set(int index, E element) {
//检查index是否在合理范围内,在则执行后续过程,不在则抛出下标越界异常
//范围:index >= 0 && index < size
checkElementIndex(index);
//获取到对应位置的结点
Node<E> x = node(index);
//更改结点存储的元素值并返回之前的值
E oldVal = x.item;
x.item = element;
return oldVal;
}
4.get()方法
获取对应位置元素的方法:
public E get(int index) {
//检查要查找位置范围
checkElementIndex(index);
//返回对应位置的结点存储的元素
return node(index).item;
}
获取头结点的元素的方法:
public E getFirst() {
final Node<E> f = first;
//如果链表为空就抛出异常
if (f == null)
throw new NoSuchElementException();
return f.item;
}
获取尾结点的元素的方法:
public E getLast() {
final Node<E> l = last;
//如果链表为空就抛出异常
if (l == null)
throw new NoSuchElementException();
return l.item;
}
实现的Deque的方法:
peek()方法、peekFirst()方法、peekLast() 方法、element()方法
//获取头结点的值,如果链表为空就返回null值
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//与peek()方法实现过程一致
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//获取尾结点的值,如果链表为空就返回null值
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
//获取头结点的值,如果链表为空就抛出异常
public E element() {
return getFirst();
}
5.size()方法
获取当前集合内存储元素的个数的方法:
public int size() {
return size;//集合内部维护了一个记录当前集合内元素个数的成员变量
}
6.contains()方法
判断当前集合内是否存在该元素:
public boolean contains(Object o) {
return indexOf(o) != -1;
}
//根据元素找对应的坐标,如果遍历完没有找到就返回-1
public int indexOf(Object o) {
int index = 0;
//null值要单独处理
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
7.clear()方法
将当前集合内存储的所有元素清除:
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
//遍历链表,断开每个节点之前的联系同时将存储的数据域置为null
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
//最后再将头结点和尾结点置为null,链表为空
first = last = null;
size = 0;
modCount++;
}
总结:
LinkedList特点:
-
可以存储重复性数据
-
可以存储null值
-
插入有序
-
底层存储的数据结构是链表
在变更数据的时候(插入、删除) 使用LinkedList,适用变更较频繁的场景。