LinkedList源码分析

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

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


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的底层数据结构,关联了前驱节点,后续节点和值。

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

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

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

 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     }

分析:

从源码上可以非常清楚的了解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日,上午。

posted @ 2019-09-02 11:27  developer_chan  阅读(1986)  评论(0编辑  收藏  举报