LinkedList概述
- LinkedLlist与ArrayList一样实现List接口,知识ArrayList是List接口的大小可变数组的实现。LinkedList是接口表的实现,基于链表实现的方式是的LinkedList在插入和删除时更优于ArrayList,而随机访问则比ArrayList差。
- LinkedList实现所有可选的列表操作,并允许所有的元素包括null。
除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作stack、deque或双端队列。 - 此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。
- LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆。
- LinkedList的实现方式决定了所有跟下标相关的操作都是线性时间,而在首段或者末尾删除元素只需要常数时间。为追求效率LinkedList没有实现同步(synchronized),如果需要多个线程并发访问,可以先采用
Collections.synchronizedList()
方法对其进行包装。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
从这段代码中我们可以清晰地看出LinkedList继承AbstractSequentialList,实现List、Deque、Cloneable、Serializable。其中AbstractSequentialList提供了 List 接口的骨干实现,从而最大限度地减少了实现受“连续访问”数据存储(如链接列表)支持的此接口所需的工作,从而以减少实现List接口的复杂度。Deque一个线性 collection,支持在两端插入和移除元素,定义了双端队列的操作。
源码
属性
在LinkedList中提供了两个基本属性size、first、last
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
transient Node<E> last;
LinkedList通过first和last引用分别指向链表的第一个和最后一个元素。注意这里没有所谓的哑元,当链表为空的时候first和last都指向null
Node节点对象
//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;
}
}
构造方法
/**
* 构造一个空列表。
*/
public LinkedList() {
}
/**
* 构造一个包含指定 collection 中的元素的列表,这些元素按其 collection 的迭代器返回的顺序排列。
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
this():首先调用LinkedList(),构造一个空列表将Collection中的所有元素添加到列表中。
add
Java1.6的addAll
addAll的源码
// **将“集合(c)”添加到LinkedList中。实际上,是从双向链表的末尾开始,将“集合(c)”添加到双向链表中**
public boolean addAll(Collection<? extends E> c){
return addAll(size, c);
}
//将指定collection中的所有元素从指定位置开始插入此列表。其中index标识在其中插入指定collection的第一个元素的索引
public boolean addAll(int index,Collection<? extends E> c){
//若插入的位置小于0或者大于链表长度,则抛出IndexOutOfBoundsException异常
if(index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
Object[] a = c.toArray();
int numNew = a.length;
//若插入的元素为空,则返回false
if (numNew == 0)
return false;
//modCount:在AbstractList中定义的,表示从结构上修改列表的次数
modCount++;
//获取插入位置的节点,若插入的位置在size头则是头结点,否则获取index位置处的节点
Entry<E> successor = (index == size? header : entry(index))
//插入位置的前一个节点,在插入过程中需要修改该节点的next引用:指向插入的节点元素
Entry<E> prodecessor = successor.previous;
//执行插入动作
for(int i = 0; i < numNew; i++){
//a[i]元素节点,successor下一个节点,predecessor上一个节点
Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);
//将插入位置前一个节点和下一个元素引用指向当前元素
predecessor.next = e;
//修改插入位置的前一个节点,这样的做的目的是插入位置右移以一位
predecessor = e;
}
sucessor.prevous = predecessor;
}
//修改容量大小
size += numNew;
return true;
在addAll()方法中,涉及了两个方法,一个是entry(int index),该方法为LinkedList的私有方法。该方法为LinkedList的私有方法,主要是用来查找index位置的节点元素。
/**
* 返回指定位置(若存在)的节点元素
*/
private Entry<E> entry(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
+ size);
//头部节点
Entry<E> e = header;
//判断遍历的方向
if (index < (size >> 1)) {
for (int i = 0; i <= index; i++)
e = e.next;
} else {
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}
从该方法有两个遍历方向中我们也可以看出LinkedList是双向链表,这也是在构造方法中为什么需要将header的前、后节点均指向自己。
java1.7的add
这里只说一下add(int index,E element)
1、 现根据index找到要插入的位置
2、 修改引用,完成插入操作
public void add(int index, E element){
checkPositionIndex(index);
if(index == size)
add(element);
else{
Node<E> succ = node(index);
//修改引用
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred,e,succ);
succ.prev = newNode;
if(pred == null)//插入位置为0
first = newNode;
else
pred.next = newNode;
size ++;
}
}
Node<E> node(int index) {
// assert isElementIndex(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;
}
}
remove
1、先找到要删除元素的引用
2、修改相关引用,完成删除操作。
在寻找被删元素引用的时候remove(Object o)调用的是元素的equals方法,而remove(int index)使用的是下标计数,两种方式都是线性时间复杂度。在
E unlink(Node<E> x){
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prec = 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--;
renturn element;
}
总结:
1. 在查找和删除某元素时,源码中都划分为该元素为null和不为null两种情况来处理,LinkedList中允许元素为null。
2. LinkedList是基于链表实现的,因此不存在容量不足的问题,所以这里没有扩容
3. 注意源码中的Entry entry(int index)方法。该方法返回双向链表中指定位置处的节点,而链表中是没有下标索引的,要指定位置出的元素,就要遍历该链表,从源码的实现中,我们看到这里有一个加速动作。源码中先将index与长度size的一半比较,如果index