LinkedList源码分析

前言:LinkedList的底层数据结构是双向链表,下面具体分析其实现原理。java

注:本文jdk源码版本为jdk1.8.0_172node


1..LinkedList介绍

LinkedList继承于AbstractSequentialList的双向链表,实现List接口,所以也能够对其进行队列操做,它也实现了Deque接口,因此LinkedList也可当作双端队列使用,还有LinkedList是非同步的。数据结构

1 java.lang.Object 2    ↳     java.util.AbstractCollection<E>
3          ↳     java.util.AbstractList<E>
4                ↳     java.util.AbstractSequentialList<E>
5                      ↳     java.util.LinkedList<E>
6 
7 public class LinkedList<E>
8     extends AbstractSequentialList<E>
9     implements List<E>, Deque<E>, Cloneable, java.io.Serializable {}

因为LinkedList的底层是双向链表,所以其顺序访问的效率很是高,而随机访问的效率就比较低了,由于经过索引去访问的时候,首先会比较索引值和链表长度的1/2,若前者大,则从链表尾开始寻找,不然从链表头开始寻找,这样就把双向链表与索引值联系起来了。函数

2.具体源码分析

LinkedList底层数据结构:源码分析

 1   private static class Node<E> {  2  E item;  3         Node<E> next;  4         Node<E> prev;  5 
 6         Node(Node<E> prev, E element, Node<E> next) {  7             this.item = element;  8             this.next = next;  9             this.prev = prev; 10  } 11     }

分析:Node为LinkedList的底层数据结构,关联了前驱节点,后续节点和值。this

构造函数,LinkedList提供了两个构造函数:spa

1  public LinkedList() { 2  } 3  public LinkedList(Collection<? extends E> c) { 4         this(); 5  addAll(c); 6     }

add函数,添加元素时,是直接添加在链表的结尾:code

 1   public boolean add(E e) {  2  linkLast(e);  3         return true;  4  }  5  void linkLast(E e) {  6         // 取出当前最后一个节点
 7         final Node<E> l = last;  8         // 建立一个新节点,注意其前驱节点为l,后续节点为null
 9         final Node<E> newNode = new Node<>(l, e, null); 10         // 记录新的最后一个节点
11         last = newNode; 12         // 若是最后一个节点为空,则表示链表为空,则将first节点也赋值为newNode
13         if (l == null) 14             first = newNode; 15         else
16             // 关联l的next节点,构成双向节点
17             l.next = newNode; 18         // 元素总数加1
19         size++; 20         // 修改次数自增
21         modCount++; 22     }

分析:blog

从源码上能够很是清楚的了解LinkedList加入元素是直接放在链表尾的,主要点构成双向链表,总体逻辑并不复杂,经过上述注释理解应该不成问题。继承

add(int,element),在具体index上插入元素:

 1 public void add(int index, E element) {  2         // 校验index是否越界
 3  checkPositionIndex(index);  4         // index和size相同则,添加在链表尾
 5         if (index == size)  6  linkLast(element);  7         else
 8             // 在index位置前插入元素
 9  linkBefore(element, node(index)); 10  } 11 // Inserts element e before non-null Node succ.
12 void linkBefore(E e, Node<E> succ) { 13         // assert succ != null;
14         final Node<E> pred = succ.prev; 15         // 建立新的节点 前驱节点为succ的前驱节点,后续节点为succ,则e元素就是插入在succ以前的
16         final Node<E> newNode = new Node<>(pred, e, succ); 17         // 构建双向链表,succ的前驱节点为新节点
18         succ.prev = newNode; 19         // 若是前驱节点为空,则first为newNode
20         if (pred == null) 21             first = newNode; 22         else
23             // 构建双向列表
24             pred.next = newNode; 25         // 元素总数自增
26         size++; 27         // 修改次数自增
28         modCount++; 29     }

分析:该函数并非直接插入链表尾,须要进行一个判断,逻辑并不复杂,经过注释应该不难理解,但这里要注意一个函数node(index),取出对应index上的Node元素,下面来具体分析一下。

 1  Node<E> node(int index) {  2         // assert isElementIndex(index);  3         // 由于这里的x不是next就是prev,当循环停止时,就是对应index上的值  4         // index若是小于链表长度的1/2
 5         if (index < (size >> 1)) {  6             Node<E> x = first;  7             // 从链表头开始移动
 8             for (int i = 0; i < index; i++)  9                 x = x.next; 10             return x; 11         } else { 12             // 从链表尾开始移动
13             Node<E> x = last; 14             for (int i = size - 1; i > index; i--) 15                 x = x.prev; 16             return x; 17  } 18     }

接下来看构造函数中的addAll方法:

 1 public boolean addAll(int index, Collection<? extends E> c) {  2         // 检查index是否越界
 3  checkPositionIndex(index);  4 
 5         Object[] a = c.toArray();  6         int numNew = a.length;  7         // 若是插入集合无数据,则直接返回
 8         if (numNew == 0)  9             return false; 10 
11         // succ的前驱节点
12         Node<E> pred, succ; 13         // 若是index与size相同
14         if (index == size) { 15             // succ的前驱节点直接赋值为最后节点 16             // succ赋值为null,由于index在链表最后
17             succ = null; 18             pred = last; 19         } else { 20             // 取出index上的节点
21             succ = node(index); 22             pred = succ.prev; 23  } 24         // 遍历插入集合
25         for (Object o : a) { 26             @SuppressWarnings("unchecked") E e = (E) o; 27             // 建立新节点 前驱节点为succ的前驱节点,后续节点为null
28             Node<E> newNode = new Node<>(pred, e, null); 29             // succ的前驱节点为空,则表示succ为头,则从新赋值第一个结点
30             if (pred == null) 31                 first = newNode; 32             else
33                 // 构建双向链表
34                 pred.next = newNode; 35             // 将前驱节点移动到新节点上,继续循环
36             pred = newNode; 37  } 38 
39         // index位置上为空 赋值last节点为pred,由于经过上述的循环pred已经走到最后了
40         if (succ == null) { 41             last = pred; 42         } else { 43             // 构建双向链表 44             // 从这里能够看出插入集合是在succ[index位置上的节点]以前
45             pred.next = succ; 46             succ.prev = pred; 47  } 48         // 元素总数更新
49         size += numNew; 50         // 修改次数自增
51         modCount++; 52         return true; 53     }

分析:逻辑并不复杂,注意一点便可,插入集合的元素是在index元素以前

其余重要的源码分析:

 1 // 经过index获取元素
 2 public E get(int index) {  3     // 检查index是否越界
 4  checkElementIndex(index);  5     // 经过node函数返回节点值 node函数前面已经分析过
 6     return node(index).item;  7 }  8 
 9 // 增长元素在链表头位置
10 private void linkFirst(E e) { 11     final Node<E> f = first; 12     // 建立新节点 前驱节点为null,后续节点为first节点
13     final Node<E> newNode = new Node<>(null, e, f); 14     // 更新first节点
15     first = newNode; 16     // 若是f为空,表示原来为空,更新last节点为新节点
17     if (f == null) 18         last = newNode; 19     else
20         // 构建双向链表
21         f.prev = newNode; 22     // 元素总数自增
23     size++; 24     // 修改次数自增
25     modCount++; 26 } 27     
28  // 释放头节点
29 private E unlinkFirst(Node<E> f) { 30     // assert f == first && f != null;
31     final E element = f.item; 32     final Node<E> next = f.next; 33     f.item = null; 34     f.next = null; // help GC 35     // 更新头节点
36     first = next; 37     if (next == null) 38         last = null; 39     else
40         // 将头节点的前驱节点赋值为null
41         next.prev = null; 42     // 元素总数自减
43     size--; 44     // 修改次数自增
45     modCount++; 46     // 返回删除的节点数据
47     return element; 48 } 49  // 释放尾节点
50 private E unlinkLast(Node<E> l) { 51     // assert l == last && l != null;
52     final E element = l.item; 53     // 和释放头节点相反,这里取出前驱节点,其余逻辑同样
54     final Node<E> prev = l.prev; 55     l.item = null; 56     l.prev = null; // help GC
57     last = prev; 58     if (prev == null) 59         first = null; 60     else
61         prev.next = null; 62     size--; 63     modCount++; 64     return element; 65 }

3.总结

总体分析下来,其实LinkedList仍是比较简单的,上面对一些重要的相关源码进行了分析,主要重点以下:

#1.LinkedList底层数据结构为双向链表,非同步。

#2.LinkedList容许null值。

#3.因为双向链表,顺序访问效率高,而随机访问效率较低。

#4.注意源码中的相关操做,主要是构建双向链表。


by Shawn Chen,2019.09.02日,上午。

相关文章
相关标签/搜索