您的位置:首页 > 编程语言 > Java开发

java集合(3):LinkedList源码分析

2017-05-25 17:22 771 查看

前言

LinkedList集合类是List 接口的链接列表实现。实现所有可选的列表操作,并且允许包含所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。

上面这段话什么意思呢?简单说就是LinkedList类不仅实现了List接口,具备列表的特性。而且也实现了Queue接口,因此也就具备了队列和栈的特性,当然可以拿来当队列或者栈来使用。那么是怎么实现队列和栈的特性呢?很简单,LinkedList实现了一组操作链表头(head)和尾(last)的操作,于是就保证了队列和栈的特性。下图展示了LinkedList类从两个接口中继承的方法。



由上图可以看到,LinkedList类实现了List接口中的一般性操作方法(add,get,remove,set等),并且实现了Deque接口中的只操作头和尾元素的方法,这些方法有个特点就是大多以xxxFirst()和xxxLast()命名。通过操作头尾的方法,就能很方便的拥有队列和栈的特性。

正文

LinkedList源码分析。

1,LinkedList类定义

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable  // 最主要的还是List,Deque接口


2, LinkedList类成员变量

transient int size = 0;   // List长度

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;
}
}


3,LinkedList类的2个构造方法

public LinkedList() {  // 空链表
}

public LinkedList(Collection<? extends E> c) { // 将已知集合按其迭代器顺序构造成链表
this();
addAll(c); // addAll()方法正是构造链表的方法
}


4,LinkedList类中来自Deque接口的常用方法(xxxFirst,xxxLast)。我们发现,类中的这部分方法大多存在相互调用的情况,其中被调用最多的当属下列4个方法,这4个方法也就构成了类中头尾操作方法体系的基础。

private void linkFirst(E e) {  // 方法1,在链表头添加一个元素
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++;
}
void linkLast(E e) {    // 方法2,在链表尾添加一个元素
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++;
}
private E unlinkFirst(Node<E> f) {  // 方法3,移除链表头元素
// 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;
}
private E unlinkLast(Node<E> l) {   // 方法4,移除链表尾元素
// 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;
}


以上面4个操作为基础,来看一下其他方法如何调用这些基础方法实现基本功能的。

add,addXXX,offer,push等添加操作,都是在调用linkFirst(),linkLast()方法

public boolean add(E e) {
linkLast(e);  // 调用基础方法
return true;
}
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);  // 调用基础方法
else
linkBefore(element, node(index));
}
public void addFirst(E e) {
linkFirst(e);  // 调用基础方法
}
public void addLast(E e) {
linkLast(e);  // 调用基础方法
}
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;
}
public void push(E e) {
addFirst(e);  // 调用基础方法
}


remove(),removeFirst(),removeLast(),poll(),pollFirst(),pollLast(),pop()等删除元素都是在调用unlinkFirst(),unlinkLast()方法。

public E remove() {
return removeFirst();  // 调用基础方法
}
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 pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);  // 调用基础方法
}
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);  // 调用基础方法
}
public E pop() {
return removeFirst();  // 调用基础方法
}


5,继承自List接口的方法,这部分方法大多跟“脚标”有关系,常用的有如下几个

public void add(int index, E element) {  // 索引处添加新元素
checkPositionIndex(index);

if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
public E get(int index) {  // 获取索引处的元素
checkElementIndex(index);
return node(index).item;
}
public E set(int index, E element) { // 替换索引处的元素
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
public E remove(int index) {  // 删除索引处的元素
checkElementIndex(index);
return unlink(node(index));
}


上面方法的共同点是都调用了node()方法,该方法返回指定索引处的元素。

Node<E> node(int 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;
}
}


6,迭代器方法,这个在上一篇已经讲过了。

总结

LinkedList的实现跟ArrayList有很大不同,这两个作为最常用的集合,各自有自己的长处和短板,以后在开发中会经常用到,需认真体会。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: