最近在面试中碰到了许多问题,发现自己的java基础还是需要好好巩固。
今天就让我们来看一下LinkedList.
LinkedList
List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。
此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。
所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。
注意,此实现不是同步的。如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须 保持外部同步。(结构修改指添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法来“包装”该列表。最好在创建时完成这一操作,以防止对列表进行意外的不同步访问,如下所示:
List list = Collections.synchronizedList(new LinkedList(…));此类的 iterator 和 listIterator 方法返回的迭代器是快速失败 的:在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。
注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何硬性保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。
上面的介绍摘自Java JDK注释。
LinkedList的底层数据结构为双向链表。(以下所有源码版本均为JDK1.7)
双向链表,每一个结点都有两个指针,一个指向他的前一个结点,一个指向他之后的一个结点
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;
}
}
LinkedList有两个构造函数。
LinkedList()
构造一个空列表。
LinkedList(Collection
public LinkedList() {
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
我们来具体分析一下LinkedList有参构造函数,即分析一下java中如何构造一个双向链表
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//在索引为index的结点前插入一个collection元素列表
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
//将指定 collection 中的元素的列表转化为数组
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
//给出两个结点变量,一个代表原index位置上的结点,一个代表index前面的一个结点
Node<E> pred, succ;
//如果该collection列表是直接放在原LinkedList之后的
if (index == size) {
//则有原index位置上的结点为null,index前面的结点为原链表的尾结点
succ = null;
pred = last;
} else {
//如果是在原链表中间加入列表,则取出原index位置上的结点和其前面的结点
succ = node(index);
pred = succ.prev;
}
//将列表插入原链表中
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//e.prev=pred;e.next=null; *(1)
Node<E> newNode = new Node<>(pred, e, null);
//如果index为0时,即为第一个结点first时,即此时需要在头结点前插入一个结点
if (pred == null)
first = newNode;
else
//如果index不为0,pred.next=newNode:与*(1),将新结点e与前一个结点联系完好联系起来,而此时e的后指针指向null;
pred.next = newNode;
//将新节点赋给pred;尾插法
pred = newNode;
}
//如果是直接在原链表后插入列表c,则在使用尾插法后只需要重新把最后的一个结点赋给last即可。
if (succ == null) {
last = pred;
} else {
//如果是在链表中部插入,则此时需要将插入的最后一个元素和原index位置上的元素链接起来
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
transient int size = 0;
LinkedList中还有另一种创建链表的方式,也是我们常用的方式
List list=new LinkedList();
list.add();
创建链表使用的尾插法
public boolean add(E e) {
linkLast(e);
return true;
}
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++;
}
在看一下add(int index, E element)
在此列表中指定的位置插入指定的元素。
看一下移除元素的操作,也就是双向链表的删除操作
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
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++;
}
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(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
//给出结点x的前后结点
final Node<E> next = x.next;
final Node<E> prev = x.prev;
//如果结点x为首结点,则首结点需要变为x的next结点;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
//如果是尾结点,则为节点需要变为x的prev结点
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
最后,我们看一下LinkedList是怎么实现关键的Iterator接口的,iterator方法的具体实现到底是什么。
首先,我们来看一下List接口中到底有哪些关于迭代器的方法
ListIterator listIterator()
ListIterator listIterator(int index)
Iterator iterator()
对于Iterator接口,我们是比较熟悉的。他有一下几个方法
boolean hasNext()
如果仍有元素可以迭代,则返回 true。
E next()
返回迭代的下一个元素。
void remove()
从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。
而ListIterator接口时继承Iterator接口的。他是列表迭代器,仅出现现实List接口的集合。。允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置。ListIterator 没有当前元素;它的光标位置 始终位于调用 previous() 所返回的元素和调用 next() 所返回的元素之间。长度为 n 的列表的迭代器有 n+1 个可能的指针位置。
void add(E e)
将指定的元素插入列表(可选操作)。
boolean hasNext()
以正向遍历列表时,如果列表迭代器有多个元素,则返回 true(换句话说,如果 next 返回一个元素而不是抛出异常,则返回 true)。
boolean hasPrevious()
如果以逆向遍历列表,列表迭代器有多个元素,则返回 true。
E next()
返回列表中的下一个元素。
int nextIndex()
返回对 next 的后续调用所返回元素的索引。
E previous()
返回列表中的前一个元素。
int previousIndex()
返回对 previous 的后续调用所返回元素的索引。
void remove()
从列表中移除由 next 或 previous 返回的最后一个元素(可选操作)。
void set(E e)
用指定元素替换 next 或 previous 返回的最后一个元素(可选操作)。
观看LinkedList源码,我们可以发现,该类并没有实现List接口的iterator方法
只有
public ListIterator listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
Q1:为什么LinkedList实现了List接口却没有实现List接口的方法呢?
这时我们再回头看看LinkedList继承的类和现实的接口
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable
我们再进入AbstractSequentialList中观察,该类继承了AbstractList。
我们看AbstractList发现
public Iterator iterator() {
return new Itr();
}
public ListIterator listIterator() {
return listIterator(0);
}
public ListIterator<E> listIterator(final int index) {
rangeCheckForAdd(index);
return new ListItr(index);
}
他现实了所有和迭代器的相关方法,因为他是直接实现List接口的。使用必须直接实现所有List接口中的集合
再看AbstractSequentialList 他重写了AbstractList的iterator方法
public Iterator iterator() {
return listIterator();
}
public abstract ListIterator listIterator(int index);
AbstractSequentialList 也实现了List接口,但没有显式的实现该接口的所有方法,因为AbstractSequentialList 的父类AbstractList实现了List接口
所以 AbstractSequentialList当然也实现了List接口,但是没有显示的实现接口中的方法
在这里我们就可以回答Q1的问题了,只要该类的父类或者基类实现了某个接口,则这个类自然就实现了这个接口,如果这个类没有显式的显示这个接口中的某些方法
,当将该类的对象赋给该接口的引用时,当调用那些方法时,会调用该类的父类或基类已显式实现的方法。
接下来,我们通过Debug来看看 list.iterator();到底是怎样调用的,最后究竟调用了哪个实现类
图1
图2
图3
图4
图5
第一次调试的时候,我一直对图3到图4这个过程有点纳闷
一直想
public ListIterator listIterator() {
return listIterator(0);
}
这个方法调用的就是自己类内部的方法
public ListIterator<E> listIterator(final int index) {
rangeCheckForAdd(index);
return new ListItr(index);
}
为什么会跳到LinkedList类中去呢
可后来转念一想凭什么是调用你自己内部的方法
Iterator it=list.iterator();
list是接口List的引用
指向的是对象是LinkedList实例。
都跟AbstractList没有直接的关系
呀,原来自己把动态绑定给忘啦!
这里之所以会调用LinkedList中的方法,是因为java的动态绑定的原理,方法的指针最终会指向我们真正new出来的对象实例中的方法。
但是如果LinkedList中没有实现这个方法,那还是会调用AbstractList内部的方法,但是如果实现了,就会根据动态绑定指向真正的实例对象中的方法
接下来看一下
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned = null;
private Node<E> next;
private int nextIndex;
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;
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;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}