一、概述
LinkedList是动态数组的另一种实现,底层以双向循环链表为实现基础,它的优势在于可以快速的删除和添加元素,不需要像ArrayList那样移动大量的元素,但对于查找元素需要逐个遍历链表中的元素,进行匹配。所以LinkedList适用于频繁删除和添加元素,较少查找元素的应用场景。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括
null)。除了实现
List 接口外,
LinkedList 类还为在列表的开头及结尾
get、
remove 和
insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、
队列或
双端队列。
此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。
所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。
注意,此实现不是同步的。如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须 保持外部同步。(结构修改指添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用Collections.synchronizedList
方法来“包装”该列表。最好在创建时完成这一操作,以防止对列表进行意外的不同步访问,如下所示:
List list = Collections.synchronizedList(new LinkedList(...));
此类的 iterator 和 listIterator 方法返回的迭代器是 快速失败 的:在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出
ConcurrentModificationException
。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。
★快速失败(快速报错):Java容器有一种保护机制,能够防止多个进程同时修改一个容器的内容,如果在迭代遍历某个容器的过程中,另一个线程介入其中,并且插入、删除或者修改此内容内的某个对象,那么就会出问题:也许迭代过程已经处理过容器中的该元素了,也许还没处理...Java容器类类库采用快速报错(fail-fase)机制,它会探查容器上的任何除了你的进程所进行的操作以外的所有变化,一旦它发现其他进程修改了此容器,就会报ConcurrentModificationException异常。
二、Node结点
链表结点定义如下:
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中也定义了两个变量分别指向链表中的第一个和最后一个结点
transient Node<E> first;
transient Node<E> last;
三、构造函数
提供两种构造函数
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
其中addAll(c) 这个方法将指定的Collection
添加到链表的结尾,
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;
Node<E> pred, succ;
if (index == size) {//succ是null,说明是在结尾添加,如果不是则记录index处的结点
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
for (Object o : a) { //双向链表的循环插入
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;//循环插入
}
if (succ == null) {
last = pred; //最后插入结点为最后一个结点
} else {
pred.next = succ;
succ.prev = pred;//插入完之后把整个链表连接起来
}
size += numNew;
modCount++;
return true;
}
最终调用的是这个 addAll
方法,这个方法是在指定位置 index
插入 Collection
,使链表后面的元素右移,第一个addAll
中传入的 index
是 size
,因此就是在结尾添加。
四、添加元素
添加元素到链表尾端:需要判断最后一个结点是否第一个结点,若是则注意把第一个结点赋值为
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++;
}
添加到succ元素之前,同样需要判断新增加的结点是否为头结点
/**
* 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++;
}
此外添加还有,大同小异,利用上述的函数,此处就不一一介绍了。
boolean | offer(E e) 将指定元素添加到此列表的末尾(最后一个元素)。 |
boolean | offerFirst(E e) 在此列表的开头插入指定的元素。 |
boolean | offerLast(E e) 在此列表末尾插入指定的元素。 |
五、删除元素
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
public E remove() {
return removeFirst();
}
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;
}
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);
}
可以看到核心方法为unlink和unlinkLast、unlinkFirst,下面看他们的具体实现
移除某个结点
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;
}
同样的unlinkFirst和unlinkLast也都只是双向链表的移除工作,思路基本类似,就不描述了,需要注意的是移除是要判断是否首节点或尾结点
清空全部结点
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
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
六、Clone
@SuppressWarnings("unchecked")
private LinkedList<E> superClone() {
try {
return (LinkedList<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
/**
* Returns a shallow copy of this {@code LinkedList}. (The elements
* themselves are not cloned.)
*
* @return a shallow copy of this {@code LinkedList} instance
*/
public Object clone() {
LinkedList<E> clone = superClone();
// Put clone into "virgin" state
clone.first = clone.last = null;
clone.size = 0;
clone.modCount = 0;
// Initialize clone with our elements
for (Node<E> x = first; x != null; x = x.next)
clone.add(x.item);
return clone;
}
调用父类的clone()方法初始化对象链表clone,将clone构造成一个空的双向循环链表,之后将header的下一个节点开始将逐个节点添加到clone中。最后返回克隆的clone对象。
LinkedList
的 Clone
方法只是简单的将原来每个node
的 item
放到克隆后的对象中,和 ArrayList
的 clone
方法一样,LinkedList
的 Clone
方法也只是浅复制,如果元素为引用类型,那么修改原 list
的值会影响克隆的list
的值。有关克隆的知识见链接点击打开链接
七、迭代
listIterator(int index)
返回此列表中的元素的列表迭代器(按适当顺序),从列表中指定位置开始。
LinkedList还有一个内部类:ListItr。
ListItr实现了ListIterator接口,可知它是一个迭代器,通过它可以遍历修改LinkedList。在LinkedList中提供了获取ListItr对象的方法:listIterator(int index)。
public ListIterator<E> listIterator(int index) {
return new ListItr(index);
}
该方法只是简单的返回了一个ListItr对象
descendingIterator()
返回以逆向顺序在此双端队列的元素上进行迭代的迭代器。
private class DescendingIterator implements Iterator<E> {
private final ListItr itr = new ListItr(size());
public boolean hasNext() {
return itr.hasPrevious();
}
public E next() {
return itr.previous();
}
public void remove() {
itr.remove();
}
}
从类名和上面的代码可以看出这是一个反向的Iterator,代码很简单,都是调用的ListItr类中的方法。
迭代实例
LinkedList<String> list = new LinkedList<String>();
list.add("First");
list.add("Second");
list.add("Thrid");
System.out.println(list);
ListIterator<String> itr = list.listIterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
try {
System.out.println(itr.next());// throw Exception
} catch (Exception e) {
// TODO: handle exception
}
itr = list.listIterator();
System.out.println(list);
System.out.println(itr.next());
itr.add("new node1");
System.out.println(list);
itr.add("new node2");
System.out.println(list);
System.out.println(itr.next());
itr.set("modify node");
System.out.println(list);
itr.remove();
System.out.println(list);
输出结果:
[First, Second, Thrid]
First
Second
Thrid
[First, Second, Thrid]
First
[First, new node1, Second, Thrid]
[First, new node1, new node2, Second, Thrid]
Second
[First, new node1, new node2, modify node, Thrid]
[First, new node1, new node2, Thrid]
把LinkedList用作堆栈、队列也只是相应的弹栈入栈操作,根本也是和添加删除一致,就不在详述。
LinkedList和ArrayList的区别
-
ArrayList
继承于AbstractList
,LinkedList
继承于AbstractSequentialList
; -
ArrayList
基于数组,LinkedList
基于双向链表,对于随机访问,ArrayList
比较占优势,对于插入删除,LinkedList
占优势; -
LinkedList
没有实现自己的Iterator
,但是有ListIterator
和DescendingIterator
; -
LinkedList
需要更多的内存,因为ArrayList
的每个索引的位置是实际的数据,而LinkedList
中的每个节点中存储的是实际的数据和前后节点的位置; -
ArrayList
和LinkedList
都是非同步的集合。