概述
- LinkedList是基于双向链表的非线性安全的一种线性的数据存储结构,由于这一特性,LinkedList还可以用作栈、队列或双端队列,LinkedList中的元素可以为空、可以重复同时又是有序的。
- 既然是基于双向链表,那么每个元素都存储了指向前一个或者后一个元素的指针域,所以可以通过任意一个元素,通过向前或者向后寻址的方式去查找到前一个元素或者后一个元素。而且,头部和尾部中也通过指针域来相互关联。
- LinkedList继承AbstractSequentialList(只能顺序访问,不能随机访问),实现了List、Deque(可以作为双端队列)、Cloneable(可以被克隆)、java.io.Serializable(支持序列化)。
初始化
- 无参构造函数
/**
* Constructs an empty list.
*/
public LinkedList() {
}
LinkedList是不需要执行扩容的,初始化的时候直接创建了一个空的集合。
- 带Collection对象的构造函数
/**
* 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);
}
首先调用无参构造方法创建一个空的集合对象,然后调用addAll方法处理传来的Collection对象。
添加元素
- 直接添加元素,代码如下所示:
/**
* Appends the specified element to the end of this list.
*
* <p>This method is equivalent to {@link #addLast}.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
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++;
}
通过注释可以很清楚的看到,这个方法是把元素作为链表的最后一个元素。现在一步步来看:
首先把链表中的最后一个节点last赋给l,然后新建一个节点newNode,其中newNode的上一个节点是l,也就是链表的最后一个节点last,下一个节点是null,然后把新定义的节点newNode赋给最后一个节点last,作为链表的尾部;
最后的判断是区别这次操作是否是第一次给链表添加元素,也就是,如果添加之前,链表的尾部节点为空,那么证明这次是第一次添加,就把本次添加的节点作为链表的头节点,如果不是第一次,那么久把这次添加的节点作为原来链表的最后一个链表尾部节点last的下一个节点。
删除元素
- 链表的删除元素如下所示:
/**
* Removes the element at the specified position in this list. Shifts any
* subsequent elements to the left (subtracts one from their indices).
* Returns the element that was removed from the list.
*
* @param index the index of the element to be removed
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
checkElementIndex是一个检测下角标是否越界的,我们直接看unlink:
/**
* Unlinks non-null node x.
*/
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;
}
这段代码总的来说,就是把当前要删除的节点X的上一个节点的下一个节点设置为X的下一个节点,X的下一个节点的上一个节点,设置为X的上一个节点,最后把X设置为null,等待被回收。
按照下角标添加元素
- 代码如下:
/**
* Inserts the specified element at the specified position in this list.
* Shifts the element currently at that position (if any) and any
* subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
第一步是检测下角标是否越界的,我们先不用管,接下来是判断:
如果要插入的元素位置和链表的长度相等,那么就执行linkLast方法,把新的元素添加到尾部,否则就调用linkBefore,把元素添加到该位置的节点的前面:
/**
* 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++;
}
本方法是在非空节点之前插入一个元素,我们看下具体操作步骤:
首先定义一个节点pred,就是上一个节点;然后定义一个新节点newNode,新节点的上一个节点是pred,下一个节点是插入前链表的当前节点,最后把newNode作为当前节点的上一个节点,如果上一个节点是null,那么就把新定义的节点作为头结点,否则就作为原链表当前节点的下一个节点。这个地方有点绕,其实就是把要插入的节点,作为一个新的节点,插入到链表的非尾部位置,把链表的某一个位置打断,把新的节点放进去并重新拼接上。
``
查找元素
- 根据下角标查找元素:
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
第一步依然是检测下角标是否越界的,不用管,直接看node(index).item:
/**
* Returns the (non-null) Node at the specified element index.
*/
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;
}
}
这是查找元素的代码,首先通过判断index和size/2的大小关系(size >> 1是位移运算,表示size/2,这种方式能提升代码运行效率),然后从index开始,分别向前后向后遍历查找元素(双向链表的特点)。
与ArrayList对比
通过分析源码,可以看出,LInkedList和ArrayList还是有很明显得区别的,本篇文章和上篇文章(ArrayList)都有涉及,主要是:
1、初始化
2、添加元素
3、删除元素
4、查找元素
总结
本文从源码出发,详细的介绍了LInkedList的特点:
- 1、LinkedList的添加删除是非常快的
- 2、LinkedList的查找需要遍历,大部分情况下性能不如ArrayList
- 3、LinkedList每一个节点中存储着上一个节点的信息和下一个节点的信息,通过此方法把所有的节点联系起来。