LinkedList继承自List,是一种常用的容器。虽然同为ArrayList和LinkedList同为List,但二者的实现方式完全不同,导致二者的性能和使用场景都有较大的不同,本文将从源码角度解析LinkedList。
LinkedList的类图关系如上图所示。简而言之,LinkedList是实现了可复制,可序列化的一种双向链表。虽然它同时实现了List和Deque接口,从内在基因上个人更倾向于将其归于双向队列。下面就从源码的角度看一看LinkedList的实现。
/**
*
* LinkedList的结点
*/
private static class Node {
E item;
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
// size of list
transient int size = 0;
/**
* Pointer to first node
*/
transient Node first;
/**
* Pointer to lastnode
*/
transient Node last;
这里列出了几个关键的变量。首先重中之重是Node,即LinkedList的结点。每一个LinkedList由一个一个的Node连接起来。Node由三部分组成,前驱指针指向前一个Node,后继指针指向后面的Node,结点元素存储值。其他几个变量从字面上也都很容易理解。
/**
* Constructs an empty list.
*/
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的构造函数有两个,一个是从集合从生成List,一个是空的构造函数。对于空的构造函数,size初始化为0,首节点和尾结点都为空指针。
LinkedList同时实现了List和Deque接口,这两种接口对数据的操作有不同的接口函数和表现形式,不过从本质上说,都是针对Node的操作,这里我们着重关注增加和删除结点,其他的接口都可以由此衍变而来。
/**
* 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));
}
/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node succ) {
// assert succ != null;
final Node pred = succ.prev;
final Node newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
/**
* Returns the (non-null) Node at the specified element index.
*/
Node node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
增加元素看add这个函数就够了。首先判断增加元素的下标是否合法,增加位置等于List长度则在尾结点后添加Node。更为一般的,则是在任意位置添加元素。添加时首先要找到这个结点要放置的位置,找位置的方式是先将List长度二分,然后根据index和二分位置的比较来决定是在前半部分还是后半部分遍历寻找。由于LinkedList遍历需要从每一个结点找到指向下一个结点的指针,再如此循环,所以这种遍历比较耗时,时间复杂度为O(n)(n为List长度)。而在LinkedList的开头和结尾处添加元素则很快,只需要O(1)常数时间。
/**
* 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));
}
/**
* Unlinks non-null node x.
*/
E unlink(Node x) {
// assert x != null;
final E element = x.item;
final Node next = x.next;
final Node 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;
}
删除结点同样也需要找到它的位置。如果前驱结点为空,说明是List首部,则后面的结点作为首结点。如果后续结点为空,则前驱结点作为List尾。如果在队列中间,则将前后两个结点连接起来,并将自身清空,缩短队列长度。可以看到,删除的操作并不复杂,主要时间在查找结点位置上,同样也是O(n)的复杂度。
/**
* 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;
}
/**
* Replaces the element at the specified position in this list with the
* specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
checkElementIndex(index);
Node x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
/**
* Adds the specified element as the tail (last element) of this list.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Queue#offer})
* @since 1.5
*/
public boolean offer(E e) {
return add(e);
}
/**
* Pushes an element onto the stack represented by this list. In other
* words, inserts the element at the front of this list.
*
* This method is equivalent to {@link #addFirst}.
*
* @param e the element to push
* @since 1.6
*/
public void push(E e) {
addFirst(e);
}
/**
* Pops an element from the stack represented by this list. In other
* words, removes and returns the first element of this list.
*
*
This method is equivalent to {@link #removeFirst()}.
*
* @return the element at the front of this list (which is the top
* of the stack represented by this list)
* @throws NoSuchElementException if this list is empty
* @since 1.6
*/
public E pop() {
return removeFirst();
}
可以看到,常用的get,set方法,主要是在找元素的位置。其他如常见的队列方法,都是在add,remove的基础上做了封装。
LinkedList是一个List,也是一个Deque,有较为丰富的接口。不同于ArrayList可以用下标找到地址,LinkedList的增删改查都需要遍历List,处理起来比较耗时,因此适用于经常对首尾元素操作且性能要求不高的场景。此外,由于LinkedList是动态申请每一块内存,对内存的连续性要求不高,在虚拟机堆内存较少的情况下可以考虑使用。