JDK源码分析– LinkedList

LinkedList类的申明

1 public class LinkedList<E>
2     extends AbstractSequentialList<E>
3     implements List<E>, Deque<E>, Cloneable, java.io.Serializable
4 {
5 }

LinkedList实现的接口与ArrayList大同小异,其中一个重要的接口Deque<E>,这个接口表示一个双向队列,也就是说LinkedList也是一个双向队列,实现了双向队列两端的增加、删除操作。

LinkedList主要字段、属性说明

 1 //记录LinkedList当前节点数
 2 transient int size = 0;
 3
 4 /**
 5  * Pointer to first node.
 6  * Invariant: (first == null && last == null) ||
 7  *            (first.prev == null && first.item != null)
 8  */
 9 //第一个节点
10 transient Node<E> first;
11
12 /**
13  * Pointer to last node.
14  * Invariant: (first == null && last == null) ||
15  *            (last.next == null && last.item != null)
16  */
17 //最后一个节点
18 transient Node<E> last;

到这里,基本可以看出来LinkedList内部使用的是双向链表的数据结构,定义了一个头节点和为节点,应用时可以通过头节点顺序遍历整个链表,也可以通过尾节点逆序遍历链表,对链表做一系列操作,所以LinkedList内部的数据结构如下:

LinkedList部分方法分析

构造函数

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

LinkedList提供了两个构造函数,无参构造函数默认初始化一个空的链表,LinkedList(Collection<? extends E> c)构造函数调用无参构造函数初始化链表,并将参数集合添加到链表中,内部通过addAll方法实现。

add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)

  • add(E e):将指定的元素追加到此列表的末尾
 1 public boolean add(E e) {
 2 //将元素添加到链表末尾
 3     linkLast(e);
 4     return true;
 5 }
 6 void linkLast(E e) {
 7   //定义l节点,将尾节点暂存在该节点上
 8     final Node<E> l = last;
 9   //创建一个新节点,用于存储将要添加的元素,该节点前驱指向原先的尾节点l
10     final Node<E> newNode = new Node<>(l, e, null);
11   //重新赋值链表的为节点,更新为新创建的节点
12     last = newNode;
13   //如果尾节点为空,则说明该链表是一个空链表,那么需要为头节点初始化
14     if (l == null)
15         first = newNode; //此时链表只有一个节点,first和last是同一个节点
16     else
17         l.next = newNode; //将原先尾节点的后继指向新创建的节点
18   //链表长度自增
19     size++;
20   //操作数自增,用于迭代器迭代校验
21     modCount++;
22 }

学过数据结构的上面这段代码很容易理解,都是单纯的链表操作

  • add(int index, E element):将指定元素插入此列表中的指定位置
 1 public void add(int index, E element) {
 2     //校验索引是否合法
 3     checkPositionIndex(index);
 4
 5     //如果索引index和链表长度相同,那么相当于在链表为节点添加元素
 6     if (index == size)
 7   //将元素添加到为节点,同add()方法内部实现
 8         linkLast(element);
 9     else
10   /创建新节点,并插入到链表指定索引处
11         linkBefore(element, node(index));
12 }
13
14 //获取原先索引为index的节点
15 Node<E> node(int index) {
16     // assert isElementIndex(index);
17     //如果索引indec位于链表前半段,则
18     if (index < (size >> 1)) {
19         Node<E> x = first;
20   //正向遍历找到索引为index的节点并返回
21         for (int i = 0; i < index; i++)
22             x = x.next;
23         return x;
24     } else {
25         Node<E> x = last;
26   //如果index位于链表后半段,则逆向遍历,查找索引为index的节点,并返回
27         for (int i = size - 1; i > index; i--)
28             x = x.prev;
29         return x;
30     }
31 }
32
33 //插入新节点
34 //E e:将要插入的元素
35 // Node<E> succ 原先index索引处的节点
36 void linkBefore(E e, Node<E> succ) {
37     //获取index-1的节点(该节点将作为即将要插入节点的前驱)
38   final Node<E> pred = succ.prev;
39   //构造新节点,并初始化前驱节点为第index-1的节点,后继节点为之前第index的节点
40     final Node<E> newNode = new Node<>(pred, e, succ);
41   //修改原先index的前驱节点,指向新创建的节点
42     succ.prev = newNode;
43   //判断第index-1的节点,如果为空,则说明之前链表首节点为空,重新赋值首节点
44     if (pred == null)
45         first = newNode;
46     else
47   //将第index-1的节点后继指向新创建的节点
48         pred.next = newNode;
49
50     size++;
51     modCount++;
52 }

这也是一个链表操作,主要就是在之前第index-1和第index两个节点之间插入一个新节点,并修改这三个节点的前驱和后继指针,使双向链表完整。

其他几个add相关的方法:

  • addAll(Collection<? extends E> c):将指定集合中的所有元素按指定集合的??迭代器返回的顺序附加到此列表的末尾
  • addAll(int index, Collection<? extends E> c):从指定位置开始,将指定集合中的所有元素插入此列表
  • addFirst(E e):在此列表的开头插入指定的元素
  • addLast(E e):将指定的元素追加到此列表的末尾

remove()、remove(int index)、remove(Object o)、removeFirst()、removeLast()

  • remove(int index):删除此列表中指定位置的元素,实现过程如下:
 1 public E remove(int index) {
 2     //校验索引的合法性
 3     checkElementIndex(index);
 4   //移除当前索引处的节点, node(index)获取索引index处的节点
 5     return unlink(node(index));
 6 }
 7 private void checkElementIndex(int index) {
 8     if (!isElementIndex(index))
 9         throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
10 }
11
12 unlink(Node<E> x) {
13     // assert x != null;
14   //暂存index处的节点
15     final E element = x.item;
16   //暂存index+1节点,后继节点
17     final Node<E> next = x.next;
18   //暂存第index-1节点,前驱节点
19     final Node<E> prev = x.prev;
20
21   //1处理前驱:如果pre节点为空,则说明x就是首节点
22     if (prev == null) {
23   //直接将原先的后继next赋值到首节点上即可
24         first = next;
25     } else {
26   //将index-1的后继指向index+1
27         prev.next = next;
28   //删除index节点的前驱
29         x.prev = null;
30     }
31
32     //2处理后继:如果next为空,说明x就是为节点
33     if (next == null) {
34   //直接将last赋值为原先节点的前驱
35         last = prev;
36     } else {
37   //将第index+1的前驱执行第index-1节点
38         next.prev = prev;
39         x.next = null;
40     }
41     //删除节点x的数据
42     x.item = null;
43   //链表长度-1
44     size--;
45     modCount++;
46     return element;
47 }

删除指定索引处的节点的实现过程就是将该节点的前驱前驱节点和后继接单 通过pre和next域连接起来。

其他几个remove相关的方法:

  • remove():检索并删除此列表的头部(第一个元素)。
  • remove(Object o):从该列表中删除指定元素的第一个匹配项(如果存在)。
  • removeFirst():从此列表中删除并返回第一个元素。
  • removeLast():从此列表中删除并返回最后一个元素。

前面一接我也分析过ArrayList的源码,那么LinkedList与ArrayList相比较有哪些区别呢:

  1. LiskedList内部是基于双向链表的存储结构,而ArrayList是基于动态数据的数据结构。
  2. 对于增加删除元素操作,ArrayList中添加和删除涉及到index+1~length-1个元素向后或向前移动,LinkedList只需要添加或删除节点,这点上优于ArrayList(尾部情况特殊,arrayList操作尾部不不要移动数据)。
  3. 对其读取和修改操作,ArrayList实现RandomAccess接口,支持高效的随机访问,而LinkedList只能通过指针遍历。
  4. 分配空间上ArrayList的扩容策略会预先分配一定量的闲置空间,linkedlist的则是按节点按需分配。

原文地址:https://www.cnblogs.com/ashleyboy/p/9573935.html

时间: 2024-08-30 08:59:41

JDK源码分析– LinkedList的相关文章

【JDK】JDK源码分析-LinkedList

概述 相较于 ArrayList,LinkedList 在平时使用少一些. LinkedList 内部是一个双向链表,并且实现了 List 接口和 Deque 接口,因此它也具有 List 的操作以及双端队列和栈的性质.双向链表的结构如下: 前文分析了 Queue 和 Deque 接口,正是因为 LinkedList 实现了 Deque 接口.LinkedList 的继承结构如下: 结点类 Node 查看 LinkedList 的源码可发现它内部有个嵌套类 Node,代码如下: private

JDK源码学习LinkedList

LinkedList是List接口的子类,它底层数据结构是双向循环链表.LinkedList还实现了Deque接口(double-end-queue双端队列,线性collection,支持在两端插入和移除元素).所以LinkedList既可以被当作双向链表,还可以当做栈.队列或双端队列进行操作.文章目录如下: 1.LinkedList的存储实现(jdk 1.7.0_51) 2.LinkedList的读取实现 3.LinkedList的性能分析 下面我们进入正题,开始学习LinkedList. L

JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlockingQueue,以便日后灵活使用. 1. 在Java的Concurrent包中,添加了阻塞队列BlockingQueue,用于多线程编程.BlockingQueue的核心方法有: boolean add(E e) ,把 e 添加到BlockingQueue里.如果BlockingQueue可以容纳,则返回true,否则抛出异常. boolean offer(E e),表示如果可能的话,将 e 加到B

JDK源码分析之String篇

------------------------------String在内存中的存储情况(一下内容摘自参考资料1)----------------------------------- 前提:先了解下什么是声明,什么时候才算是产生了对象实例 其中x并未看到内存分配,变量在使用前必须先声明,再赋值,然后才可以使用.java基础数据类型会用对应的默认值进行初始化 一.首先看看Java虚拟机JVM的内存块及其变量.对象内存空间是怎么存储分配的 1.栈:存放基本数据类型及对象变量的引用,对象本身不存放

【JDK源码分析】通过源码分析CyclicBarrier

前言 CyclicBarrier它是什么?一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点.类似于朋友之间联系要在中午聚个会,几个朋友全部到齐后才开始喝酒吃菜. 源码 CyclicBarrier属性和构造器 public class CyclicBarrier { // 互斥锁 private final ReentrantLock lock = new ReentrantLock(); // 条件等待 private final Condition trip = lock.new

【JDK】JDK源码分析-CountDownLatch

概述 CountDownLatch 是并发包中的一个工具类,它的典型应用场景为:一个线程等待几个线程执行,待这几个线程结束后,该线程再继续执行. 简单起见,可以把它理解为一个倒数的计数器:初始值为线程数,每个线程结束时执行减 1 操作,当计数器减到 0 时等待的线程再继续执行. 代码分析 CountDownLatch 的类签名和主要方法如下: public class CountDownLatch {} 常用方法为:await().await(long, TimeUnit) 和 countDow

【JDK】JDK源码分析-Semaphore

概述 Semaphore 是并发包中的一个工具类,可理解为信号量.通常可以作为限流器使用,即限制访问某个资源的线程个数,比如用于限制连接池的连接数. 打个通俗的比方,可以把 Semaphore 理解为一辆公交车:车上的座位数(初始的“许可” permits 数量)是固定的,行驶期间如果有人上车(获取许可),座位数(许可数量)就会减少,当人满的时候不能再继续上车了(获取许可失败):而有人下车(释放许可)后就空出了一些座位,其他人就可以继续上车了. 下面具体分析其代码实现. 代码分析 Semapho

jdk源码分析总览

今天看到了一个源码分析按照重要性排序的例子, 这里拿过来用了,之后按照这个顺序不断的完善源码的内容. 引用的出处忘记了(对作者说声抱歉) 很多java开发的小伙伴都会阅读jdk源码,然而确不知道应该从哪读起.以下为小编整理的通常所需阅读的源码范围. 标题为包名,后面序号为优先级1-4,优先级递减 1.java.lang 1) Object 12) String 13) AbstractStringBuilder 14) StringBuffer 15) StringBuilder 16) Boo

JDK源码分析之concurrent包(三) -- Future方式的实现

上一篇我们基于JDK的源码对线程池ThreadPoolExecutor的实现做了分析,本篇来对Executor框架中另一种典型用法Future方式做源码解读.我们知道Future方式实现了带有返回值的程序的异步调用,关于异步调用的场景大家可以自行脑补Ajax的应用(获取返回结果的方式不同,Future是主动询问获取,Ajax是回调函数),这里不做过多说明. 在进入源码前,首先来看下Future方式相关的API: 接口Callable:有返回结果并且可能抛出异常的任务: 接口Future:表示异步