LinkedList最大的特点是它的数据结构,它的底层是使用链表结构实现。链表是线性表的一种。而链表又分为单向链表和双向链表,链表又有循环与非循环之分,在jdk1.6中就是使用了双向循环链表的结构,而在jdk1.7和jdk1.8中改成了双向链表,也就是取消循环了。本文主要分析jdk1.8中LinkedList的源码,当然,下面我也会放上几种链表的图解说明
单向链表中每个节点都有一个指向下一个节点的指针next,最后一个节点的指针next指向null
单向循环链表是通过最后一个节点(tail)的指针指向第一个节点(head),形成一个闭环
双向链表中每个节点都会有两个指针,pre指向前一个节点,next指向下一个节点,第一个节点的pre指向null,最后一个节点的next指向null,达到两个方向互通
第一个节点中的pre指向最后一个节点,最后一个节点中的next指向第一个节点,形成一个闭环,且双向互通
打开源码我们可以看到LinkedList继承的类与实现的接口
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable
{
transient int size = 0; //节点的个数(被transient修饰的变量不参与序列化过程)
transient Node first; //表示链表第一个节点
transient Node last; //表示链表最后一个节点
private static final long serialVersionUID = 876323262645176354L;
}
这里的Node节点是在内部定义的一个类型,下面看看这个内部类有哪些属性以及如何构造的:
两个构造方法,LinkedList(Collection extends E>) 允许传入一个集合,查看源码可以看到调用了内部addAll 方法,稍后与add方法一同介绍
add(E)方法默认往linkedList末尾添加新元素
源码:
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node l = last; //链表中最后一个节点 last
final Node newNode = new Node<>(l, e, null); //new 一个新的节点 newNode,将 prev 指针指向 l ,e 作为新节点元素,next 指向 null
last = newNode; //新添加的 newNode 变为该 LinkedList 的最后一个节点 last
if (l == null)
first = newNode; // 若 l 节点不存在,则将新节点 newNode 作为第一个节点 first
else
l.next = newNode; // l 节点存在,将 l 节点的 next 指针指向 新节点 newNode
size++; //最后长度加一
modCount++;
}
执行大致过程可以看下图:
add(int, E) 方法在指定位置添加新的元素
源码:
public void add(int index, E element) {
checkPositionIndex(index); //对指定元素位置进行合理性检查
if (index == size)
linkLast(element); //index == size 时,调用 linkLast 方法将元素添加到 linkedList 末尾
else
linkBefore(element, node(index)); //调用 linkBefore 方法
}
//元素位置越界检查异常处理
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//判断元素位置是否合理
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
分析 linkBefore() 方法之前,先看看 node(index) 对节点位置做了怎样的处理。
/**
* 返回指定元素索引处的(非空)节点
* Returns the (non-null) Node at the specified element index.
*/
Node node(int index) {
if (index < (size >> 1)) { //size >> 1 相当于 size/2
Node x = first; //从头部第一个节点开始 赋值给 x
for (int i = 0; i < index; i++)
x = x.next; // 每次都将下一个节点赋值给 x
return x; // 最后返回的 x 为 index 位置的节点
} else {
Node x = last;
for (int i = size - 1; i > index; i--) //这里和上面一样,区别在于从尾部最后一个节点开始遍历
x = x.prev;
return x;
}
}
在linkedList中没有下标的概念,但是它又能够在指定的位置插入元素,看完上面的代码,很显然它是通过遍历找到指定位置的元素,这里作者巧妙的利用了二分法,让遍历的效率提高一倍
再看看 linkBefore() 方法的实现:
/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node succ) {
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++;
}
这个 linkBefore 方法实现原理与 linkLast 方法类似,将新节点 newNode 的 prev 指针指向指定位置的节点 succ 的前一个节点 pred ,而 succ 的 prev 指针变为指向新节点 newNode, 而新节点 newNode 的 next 指针则指向 succ ,pred 节点的 next 指针指向 newNode ; 这里文字有点绕, 简单点讲: 就是将新节点 newNode 插入 指定节点 succ 的位置,再将它们的指针重整。下面放上一张动图,可以很清晰的看到 linkBefore 方法的大致执行流程:
addAll(Collection extends E> c) 方法内部调用的是addAll(int index, Collection extends E> c) 方法,这里只分析后一个
addAll(int index, Collection extends E> c) 方法: 在指定位置插入一个集合,linkList有改动返回true,否则返回false,源码如下:
public boolean addAll(int index, Collection extends E> c) {
checkPositionIndex(index); //指定位置合理性检查
Object[] a = c.toArray(); // 将指定集合转换成数组 a
int numNew = a.length; // 保存数组的长度
if (numNew == 0)
return false; // 数组没有元素返回false
Node pred, succ; // 定义 pred 和 succ 节点,将要在这两个节点中间插入新的节点
if (index == size) {
succ = null; // 当指定位置 index 等于链表长度时,该插入位置为链表末端,不存在 succ 节点
pred = last; // 则 pred 为最后一个节点 last
} else {
succ = node(index); // 将指定位置index的节点赋值给 succ
pred = succ.prev; // succ 的 prev 指针指向 pred
}
for (Object o : a) { // for 循环遍历 a 数组
@SuppressWarnings("unchecked") E e = (E) o;
Node newNode = new Node<>(pred, e, null); // 将新节点 e 的 prev 指针指向 pred 节点,next 指针先指向 null ,得到新节点 newNode
if (pred == null) // 不存在前一个节点 pred 时
first = newNode; // 则新节点 newNode 为链表头一个节点 first
else
pred.next = newNode; //存在前一个节点 pred 时,pred 的 next 指针指向新节点 newNode
pred = newNode; // 最后将新节点赋值为 pred 供下次循环使用,逐个将数组 a 中的元素插入到链表中
}
if (succ == null) { // 遍历完后,如果不存在 succ 节点
last = pred; // 则数组 a 中最后一个元素 pred 就是链表末端最后一个节点 last
} else { // 如果存在 succ 节点
pred.next = succ; // 则数组 a 中最后一个元素 pred 节点的 next 指针指向 succ 节点
succ.prev = pred; // succ 的 prev 指针指向 数组 a 中最后一个元素 pred 节点
}
size += numNew; // 更新链表长度
modCount++;
return true; // 链表改动,返回 true
}
小结: addAll 方法实现步骤:
addFirst(E) 和 addLast(E)这两个方法分别调用了 linkFirst(E) 和 linkLast(E) ,两个方法实现大同小异,看源码:
只要读懂前面的分析过程,这里就很容易看懂了,当我们要往链表头部插入一个新的节点时,我们只要将新节点的 prev 指针指向 null ,next 指针指向原链表的第一个节点,如此,指定的新元素就成为链表的第一个节点 first ;在链表尾部插入新元素也是同理,只是指针的方向改为指向 last 节点就可以了
remove() 方法删除链表的第一个元素
源码分析:
/**
* Retrieves and removes the head (first element) of this list.
* 检索并删除列表的头部第一个元素
* @return the head of this list
* @throws NoSuchElementException if this list is empty
* @since 1.5
*/
public E remove() {
return removeFirst();
}
/**
* Removes and returns the first element from this list.
* 从列表中移除并返回第一个元素
* @return the first element from this list
* @throws NoSuchElementException if this list is empty
*/
public E removeFirst() {
final Node f = first;
if (f == null)
throw new NoSuchElementException(); //first 节点为 null 时,说明该链表上没有元素,抛出异常
return unlinkFirst(f);
}
/**
* first 节点不为空时,断开 first 节点指针
* Unlinks non-null first node f.
*/
private E unlinkFirst(Node f) {
// assert f == first && f != null;
final E element = f.item; // 将指定删除节点的元素 item 赋值给新定义的 element ,在方法结束时返回 element
final Node next = f.next; // 将 f 节点的 next 指向的节点赋值给新定义的 next 节点
f.item = null; // first 节点的 item 设置为 null
f.next = null; // first 节点的 next 设置为 null
first = next; // first 节点的下一个节点 next 作为第一个节点 first
if (next == null)
last = null; // next 节点为 null 时,说明链表只有一个节点 first ,不存在节点 last ,所以 last 要设置为 null
else
next.prev = null; // next 在这里作为删除 first 节点后的第一个节点,故 next 的 prev 指针应该指向 null
size--; // 链表长度减一
modCount++;
return element; // 最后返回指定删除的元素 element
}
remove() 方法实现大致分为三步:
remove(int) 方法删除指定位置的一个元素
源码分析:
public E remove(int index) {
checkElementIndex(index); //指定位置的合理性检查
return unlink(node(index)); // node(index) 上面分析过,返回 index 位置的的节点,接着调用 unlink 方法
}
/**
* 删除指定不为空的元素
* Unlinks non-null node x.
*/
E unlink(Node x) {
// assert x != null;
final E element = x.item; // 将指定位置节点 x 的属性 item 保存为 element ,方法结束时返回 element
final Node next = x.next; // 将 x 节点的 next 指针指向新定义的 next 节点
final Node prev = x.prev; // 将 x 节点的 prev 指针指向新定义的 prev 节点
if (prev == null) {
first = next; // 当 prev 指针指向 null 时,说明指定删除的节点位置为链表首节点 first ,将 next 节点设置为 first 节点
} else {
prev.next = next; // 否则将 next 节点设置为新定义的 prev 节点的 next 节点
x.prev = null; // 断开指定删除节点的 prev 指针指向的节点
}
if (next == null) {
last = prev; // 当 next 指针指向的节点为 null 时,prev 节点作为最后一个节点 last
} else {
next.prev = prev; // 否则将 prev 节点设置为新定义的 next 节点的 prev 节点
x.next = null; // 断开指定删除节点的 next 指针指向的节点
}
x.item = null; // 将指定删除的节点的 item 属性设置为 null,让 GC 清理
size--; // 链表长度减一
modCount++;
return element; // 最后返回 element 元素
}
remove(int index) 方法大致分为以下五步:
LinkedList remove(o) 方法支持传入一个Object类型,如果链表中包含指定元素,则删除,返回 true;如果不包含则返回 false
源码分析:
public boolean remove(Object o) {
if (o == null) {
for (Node x = first; x != null; x = x.next) { // 传入的参数为 null 时,从 first 节点开始 for 循环遍历整个链表
if (x.item == null) { // 当节点的 item 属性为 null 时
unlink(x); // 删除该节点(unlink方法上面分析过)
return true; // 返回 true
}
}
} else { // 要删除的元素不为 null 的情况
for (Node x = first; x != null; x = x.next) {
if (o.equals(x.item)) { // equals 方法判断元素是否同一元素
unlink(x); // 同一个元素,则删除该元素
return true; // 返回 true
}
}
}
return false; // 如果指定删除的元素在链表中不存在时,返回false
}
这里需要注意的是,在LinkedList 中可以存在 null 值的,而这个 null 值是存储在节点的 item 属性中,节点还有 prev 和 next 指针分别指向前一个元素和后一个元素,prev 和 next 不是空的,所以这也是LinkedList 中可存放 null 值的合理性因素。
这两个方法顾名思义,与 addFirst() 和 addLast() 相对应,既然支持链表首尾添加,就应该支持首尾删除,看看它们的源码:
public E removeFirst() {
final Node f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
public E removeLast() {
final Node l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
这里两个方法分别调用 unlinkFirst() 和 unlinkLast() , unlinkFirst 方法在上面讲 remove() 的时候分析过,unlinkeLast() 就大同小异了。这里不再对这两个方法做分析,可以参考本文 3.5.1
先上源码:
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
这两个方法与 remove(Object o) 方法相似,只不过remove(Object o) 默认从链表头部开始遍历删除第一次出现的指定元素,removeFirstOccurrence(Object o) 就是调用了remove(Object o) 方法;而removeLastOccurrence(Object o) 实现了从链表尾部开始遍历删除指定元素,看到源码,我们可以知道它的实现方式与 remove(Object o) 相似,不同的是遍历的方向相反而已。
LinkedList 支持链表首末添加/删除元素操作
LinkedList 支持指定位置插入/删除元素
LinkedList 允许删除指定元素
LinkedList 支持存放 null 值
set(int index, E element) 将链表中指定位置 index 的元素替换成指定元素 element
public E set(int index, E element) {
checkElementIndex(index); // 指定位置的合理性检查
Node x = node(index); // 获取指定位置的节点对象
E oldVal = x.item; // 将指定位置的节点对象 x 的 item 属性赋值给 oldVal
x.item = element; // 将指定的元素 element 赋值给 x 节点的 item 属性。
return oldVal; // 将 oldValue 返回
}
LinkedList 的数据都是存储在每个节点的 item 属性当中,我们修改数据时,只要修改对应位置节点的 item 属性就行了。
get(int) 方法允许获取链表中指定位置的元素。
源码:
/**
* 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); // 对指定位置 index 做合理性检查
return node(index).item; // 获取到 index 位置对应节点对象的 item 属性值,然后返回
}
get(int) 方法的实现调用了 node(index) 方法,该方法在本文 3.4.2 中分析过。方法内部主要是根据指针 for 循环遍历找到对应位置的节点,显然这种查找方式效率很慢,任何事情都不能做的完美,在这种数据结构下,已经决定了它自身的查找效率是个短板。作者希望能够尽可能的提高它的查询效率,在遍历之前使用了二分法,查找效率提高了一倍。
顾名思义,获取链表第一个元素和获取链表最后一个元素。
源码:
public E getFirst() {
final Node f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
public E getLast() {
final Node l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
这两个方法很容易看懂,就三步,赋值、对节点做异常处理、返回节点的 item 属性。
Deque 是双端队列,基本操作与 LinkedList 相似,看源码:
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 E peekFirst() {
final Node f = first;
return (f == null) ? null : f.item;
}
public E peekLast() {
final Node l = last;
return (l == null) ? null : l.item;
}
public E pollFirst() {
final Node f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E pollLast() {
final Node l = last;
return (l == null) ? null : unlinkLast(l);
}
public void push(E e) {
addFirst(e);
}
public E pop() {
return removeFirst();
}
public void push(E e) {
addFirst(e);
}
public E pop() {
return removeFirst();
}
可以看到 Deque 的所有操作都基于 LinkedList 内部方法的实现