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. public LinkedList(Collection<? extends E> c) {
  5. this();
  6. addAll(c);
  7. }

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的则是按节点按需分配。

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

  1. 【JDK】JDK源码分析-LinkedList

    概述 相较于 ArrayList,LinkedList 在平时使用少一些. LinkedList 内部是一个双向链表,并且实现了 List 接口和 Deque 接口,因此它也具有 List 的操作以及 ...

  2. JDK源码分析(三)—— LinkedList

    参考文档 JDK源码分析(4)之 LinkedList 相关

  3. JDK源码分析(2)LinkedList

    JDK版本 LinkedList简介 LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作. LinkedList 实现 ...

  4. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

  5. JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable

    JDK 源码分析(4)-- HashMap/LinkedHashMap/Hashtable HashMap HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时 ...

  6. JDK源码分析(一)—— String

    dir 参考文档 JDK源码分析(1)之 String 相关

  7. 【JDK】JDK源码分析-LinkedHashMap

    概述 前文「JDK源码分析-HashMap(1)」分析了 HashMap 主要方法的实现原理(其他问题以后分析),本文分析下 LinkedHashMap. 先看一下 LinkedHashMap 的类继 ...

  8. 【JDK】JDK源码分析-HashMap(1)

    概述 HashMap 是 Java 开发中最常用的容器类之一,也是面试的常客.它其实就是前文「数据结构与算法笔记(二)」中「散列表」的实现,处理散列冲突用的是“链表法”,并且在 JDK 1.8 做了优 ...

  9. 【JDK】JDK源码分析-TreeMap(2)

    前文「JDK源码分析-TreeMap(1)」分析了 TreeMap 的一些方法,本文分析其中的增删方法.这也是红黑树插入和删除节点的操作,由于相对复杂,因此单独进行分析. 插入操作 该操作其实就是红黑 ...

随机推荐

  1. 2.1 摄像头V4L2驱动框架分析

    学习目标:学习V4L2(V4L2:vidio for linux version 2)摄像头驱动框架,分析vivi.c(虚拟视频硬件相关)驱动源码程序,总结V4L2硬件相关的驱动的步骤:  一.V4L ...

  2. C语言之二叉树

    规定:根节点的值大于左节点但小于右节点的值,所以二叉树的值插入是唯一的,最后形成的树只跟根节点有关 定义节点: struct tree_node {      TypeElem  elem; stru ...

  3. IAP笔记

    1)   首先是IAP BootLoader程序设置:根据common.h里面的宏定义,设置BootLoader所占用的Flash空间. 2)   设置IAP UserApp程序设置:该型号Flash ...

  4. Python学习3——变量如何存储数据

    数值类型:包括整型.浮点型 变量名字代表的是存储地址. num01 = 100 print(id(num01)) #输出变量num01存储的内存地址,输出的是十进制值 num02 = num01 pr ...

  5. 深圳Uber优步司机奖励政策(12月28日到1月3日)

    滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...

  6. 长沙优步Uber奖励政策(7.27~8.2)

    奖励前提 *必须满足当周评分4.7星及以上,且当周接单率70%及以上,才有资格获得奖励 *当周上线时间不低于7小时 *刷单和红线行为立即封号并取消当周全部奖励! *机场高速费用不参与翻倍奖励 *早高峰 ...

  7. sqlite helper

    //-------------------------------------------------------------------------- // // Copyright (c) BUS ...

  8. springBoot cache操作2

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/zxd1435513775/article/details/85091793一.基本项目搭建测试项目是 ...

  9. MySQL高级-全局查询日志

    注意:全局查询日志不要在生成环境中启用 一.配置启用 二.编码启用

  10. DMA是什么意思

    DMA是让硬盘不用通过CPU来控制读写 它的意思是直接存储器存取,是一种快速传送数据的机制,DMA技术的重要性在于,利用它进行数据存取时不需要CPU进行干预,可提高系统执行应用程序的效率.利用DMA传 ...