• 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日,上午。

  • 相关阅读:
    ES5和ES6中的静态方法、类、单例模式
    Koa 应用生成器以及 Koa 路由模块化
    封装 Koa操作Mongodb数据库的DB类库
    Koa Session 的使用
    Koa 中 Cookie 的使用
    koa art-template 模板引擎
    koa koa-static 静态资源中间件
    koa post 提交数据 koa-bodyparser 中间件的使用
    koa ejs 模板引擎
    详解express与koa中间件执行顺序模式分析
  • 原文地址:https://www.cnblogs.com/developer_chan/p/11439711.html
Copyright © 2020-2023  润新知