-LinkedList定义
LinkedList是一个双端列表,官方是这样描述的:
List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。
注意,此实现不是同步的。如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须 保持外部同步。
在来看LinkedList类的定义:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
实现了List能够提供链表服务,实现了Deque能够提供队列服务。
-成员变量
transient int size = 0;//链表中结点的个数
transient Node<E> first;//整个链表的头结点
transient Node<E> last;//整个链表的尾结点
//LinkedList的静态内部类,第一结点的类结构
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;
}
}
-构造函数
//无参数构函
public LinkedList() {
}
//传入集合构建链表
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);//将集合中的元素全部添加到链表中去,后面会详细解析。
}
-添加操作全家桶
添加操作很多都是复用了其他的函数,这里我们选了几个有代表性的来解析
public void addFirst(E e) {
linkFirst(e);//将元素添加到头部,下面的详细解析
}
//在头部添加,也就是添加的结点作为头结点
private void linkFirst(E e) {
final Node<E> f = first;//将原本的头结点保存
//创建一个新的结点,他没有前驱结点,值为e,后继结点就是以前的头结点
final Node<E> newNode = new Node<>(null, e, f);
//将新的结点设置为头结点
first = newNode;
//如果头结点是null,说明整个链表之前就是空,所以将尾指针也指向新添加的结点
if (f == null)
last = newNode;
else
//否则的话,将原本头结点的前驱结点设置为现在的头结点,也就是刚刚添加的新结点。
f.prev = newNode;
size++;//链表的总容量增加一个
modCount++;
}
linkFirst(E e)主要将e设置为链表的头结点,之前的头结点就在第二位了,也就是它的后面。
//这两个方法做的事情相同,都是在链表尾添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
public void addLast(E e) {
linkLast(e);//下面是详解
}
//在尾部添加结点
void linkLast(E e) {
//保存尾结点
final Node<E> l = last;
//创建新结点,他的前驱结点是之前的尾结点,值为e,后继结点为null,因为它自己将成为尾结点
final Node<E> newNode = new Node<>(l, e, null);
//将新的结点设置为尾结点
last = newNode;
//如果尾结点为null,那么说明之前整个链表就是空,那么把头指针也指向新结点
if (l == null)
first = newNode;
else
//否则的话,把之前尾结点的后继结点指向现在尾结点也就是新添加的结点。
l.next = newNode;
size++;
modCount++;
}
//在指定位置前添加元素
public void add(int index, E element) {
checkPositionIndex(index);//健壮性检查,检查index是否合法。
if (index == size)
linkLast(element);//如果插入的位置在链表尾就直接调用在链表尾插入的方法了
else
//否则在指定位置插入,第一个参数是要插入的元素,第二个参数是index位置原来的结点。
linkBefore(element, node(index));
}
接下来,我们来看node(index)
Node<E> node(int index) {
//这是官方注释的不知道是干哈的
// assert isElementIndex(index);
//判断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 = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
我们再回过头来看linkBefore(element, node(index))方法。
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;//succ就是index位置上的结点
//创建新结点,它的前驱结点是index结点的前驱结点,值为e,后继结点就是index位置上的结点
final Node<E> newNode = new Node<>(pred, e, succ);
//将之前index位置上的结点的前驱结点设置为新结点
succ.prev = newNode;
//如果index位置的前驱结点为null,说明index位置的结点是投结点,要在头结点之前插入元素,那么插入的元素就是新链表的头结点
if (pred == null)
first = newNode;
else
//否则的话,之前index位置上的(结点的前驱结点)的后继结点 指向新结点。(上一个括号是为了方便断句)
pred.next = newNode;
size++;//链表总容量增加
modCount++;
}
接下来看一下,之前构造函数的addAll函数。
addAll是一个重载的方法,一个是有一个参数,一个是有两个参数。
//一个参数的在内部调用了两个参数的
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//在指定位置后,在链表中添加指定的集合元素
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);//健壮性检查
Object[] a = c.toArray();//结合数组化
int numNew = a.length;//集合的容量
if (numNew == 0)
return false;//如果集合为空,返回false
Node<E> pred, succ;//新添加元素的前驱和后继结点
//如果插入的位置是在链表尾,那么将一个集合元素的后继设置为空,前驱结点就是之前链表的尾结点。
if (index == size) {
succ = null;
pred = last;
} else {
否则,先找出index位置的结点,并将其前驱结点设置为一个集合元素的前驱结点
succ = node(index);
pred = succ.prev;
}
//循环添加结点
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//创建新结点,他的前驱结点在上面已经设置过了,值为e,后继结点等到null(后继结点等到下一次循环时设置)
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)//如果前驱结点是null,那么新插入的元素就是头结点
first = newNode;
else
//否则就把它的前驱结点的后继结点指向新结点
pred.next = newNode;
//将新结点作为下一个要添加的结点的前驱结点进入下一次循环
pred = newNode;
}
//将前面的链表和后面连接在一起
if (succ == null) {//如果插入的位置是链表尾,就把最后一个插入的元素设置为尾结点
last = pred;
} else {
否则的话,集合中最后一个元素的后继结点设置为index位置上的结点
index位置上的结点的前驱结点设置为集合中最后一个元素的后继结点
pred.next = succ;
succ.prev = pred;
}
size += numNew;//集合容量增加了插入的集合的长度个
modCount++;
return true;
}
入队操作,实际调用的还是add()方法
public boolean offer(E e) {
return add(e);
}
//当LinkedList被用作Stack时,入栈操作
public void push(E e) {
addFirst(e);
}
//一个特殊的添加操作,实际是替换了index位置上的值
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);//找出index位置上的结点
E oldVal = x.item;
x.item = element;
return oldVal;//替换后返回
}
-获取操作全家桶
//返回指定位置的结点
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 element() {
return getFirst();
}
//出栈操作,不删除栈顶元素
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//获取尾元素
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
-获取位置
//从头结点开始获取第一个与指定元素相同的值的位置
public int indexOf(Object o) {
int index = 0;
if (o == null) {//当参数是null,找出链表中第一个值为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;//否则返回-1表示没找到
}
//相似的结构,这个是从尾结点向前找,意义是返回链表中最后一个与指定元素相等的位置。
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
-删除全家桶
删除操作也有很多复用其他函数的情况,这里我们也挑主要的函数来解析
//从权限上看,这个函数就是被内部调用的函数,主要用来删除头结点
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;//如果next为空表示链表中只有一个结点
else
next.prev = null;
size--;
modCount++;
return element;
}
//同上面的函数的结构相似,这个函数是删除尾结点
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;
}
//删除指定结点,整体思想就是让指定结点的前驱结点直接指向其后继结点
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;//前驱结点的next指针直接指向后继结点
x.prev = null;
}
if (next == null) {
last = prev;//如果后继结点为空,说明该结点是尾结点
} else {
next.prev = prev;//该后继结点的prev指针指向前驱结点(因为是双向链表,所以删除一个元素要调动两根指针)
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
//根据指定的元素,先找出指定的结点,再调用unlink(x)删除该结点
public boolean remove(Object o) {
if (o == null) {//如果参数为null,就删除一个元素为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;
}
//用户操作的删除头结点的接口
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//删除尾指针
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
//从头出队操作(由于是双向队列)
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//出栈操作
public E pop() {
return removeFirst();
}
-转换数组操作
//这里也是用来常规的循环遍历的方法
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
-迭代器
//传统的Iterator在继承的AbstractSequentialList里已经实现了
//LinkedList自己内部遍历器listIterator可以实现从指定位置遍历
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
//它是一个私有的内部类
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;//最终返回的结点
private Node<E> next;//下一个结点
private int nextIndex;//结点位置
//exceptedModCount保存了当前的modCount,如果在迭代期间,有操作改变了链表的底层结构,那么再操作迭代器的方法时将会抛出ConcurrentModificationException。
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
//找出指定位置的结点
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
//判断后面是否还有结点
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
//如果不为空,就返回当前结点
lastReturned = next;
//next指向下一个结点
next = next.next;
//位置加一,指向下一个结点
nextIndex++;
return lastReturned.item;
}
public boolean hasPrevious() {
//由指定位置向前遍历,判断前面是否还有结点
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
//向头结点方向遍历
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
public int nextIndex() {
//返回当前遍历的位置
return nextIndex;
}
public int previousIndex() {
//返回当前遍历的前一个位置
return nextIndex - 1;
}
public void remove() {
checkForComodification();//进行操作的判断,如果当前有两个线程在修改,那么会抛异常
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);//删除当前遍历的结点
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
//赋值给当前遍历的结点
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
//在当前遍历到的结点之后插入指定结点,也就是在next()返回的结点之后插入指定结点
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);//如果下一个结点是null,说明要插入的位置是尾结点
else
linkBefore(e, next);//否则在next之前插入(经过了next()调用之后,next=next.next,所以要在这时的next之前插入)
nextIndex++;
expectedModCount++;
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}