JavaSE基础知识(二十一)--Java集合(容器)之类LinkedList的源码分析(已修改)

本文详细分析了Java中的LinkedList集合类,作为List和Deque接口的实现,LinkedList的特点在于其双链表结构。文章指出LinkedList非线程安全,并提供了在多线程环境下保证安全的方法。此外,还介绍了LinkedList的节点结构、内部类Node以及各种添加、删除和访问元素的方法。
摘要由CSDN通过智能技术生成

Java SE 是什么,包括哪些内容(二十一)?

本文内容参考自Java8标准
顺序访问列表的一个常用类:

LinkedList

类声明:

   public class LinkedList<E> extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
   {
   
      ...
   }

源代码图示(图下面有解释说明):
LinkedList源码
英文注释分别阐述了以下内容(一个段落用一个小标):
⑴、LinkedList(双链列表)同时实现了接口List和Deque(LinkedList实现了这两个接口的所有方法),它既是一个列表,也是一个双端队列。它允许添加任何元素,包括null元素。
⑵、LinkedList所有方法的实现都体现了双链表的特性,所有有关索引的操作都自动从LinkedList的开头或结尾开始遍历(如果索引更接近开头,自动从开头开始遍历,更接近结尾,自动从结尾开始遍历)。
⑶、LinkedList不是线程安全的,如果在多线程环境中涉及结构修改(结构修改是指添加或删除一个或多个元素),必须从外部保证LinkedList的线程安全(通过对持有LinkedList的对象进行同步来完成)。如果不存在持有LinkedList的对象,应该使用类Collections的方法synchronizedList(new LinkedList(…)),具体的代码如下:

List list = Collections.synchronizedList(new LinkedList(...));

最好在创建LinkedList对象时就这么做,以防止任何不安全的操作发生。
⑷、在获取了迭代器或列表迭代器后,除非使用它们自身提供的方法(方法remove()和add())修改LinkedList的结构,否则都将抛出ConcurrentModificationException异常(快速失败机制)。迭代器不会在未来的不确定时间内冒任意不确定行为的风险(如果在多线程环境的迭代过程中修改了LinkedList的结构,可能返回很多种结果)。
⑸、注意,无法保证快速失败机制一定会执行,只能说迭代器会尽最大的努力抛出ConcurrentModificationException异常。因此,通过快速失败机制保证程序的正确性是错误的,它通常只能用于检测错误。
⑹、LinkedList是Java Collections Framework中的重要一员。
在分析类LinkedList的源码之前,必须先分清楚"节点"和"元素",它们是两个不同的概念。
⑺、节点的图示:
LinkedList中的每个节点都是一个单独的存储空间,"元素"存储在节点中,但节点中不仅只有元素。ArrayList的底层是数组,数组是连续的存储空间(通过持有数组的引用+"[ 索引 ]"+访问),所以每个存储空间只需存储元素,不需要存储相互之间的关系。而LinkedList底层是链表(双向),链表不是连续的存储空间,所以每个存储空间(节点)不仅需要存储元素,还需要存储相互之间的关系(prev:上一个节点的位置,如果有。next:下一个节点的位置,如果有)。
节点的图示
⑻、链表的图示:
在链表中,一个独立的存储空间就是一个节点,同时,一个节点就是一个内部类Node的对象实例。链表是由若干个节点构成的,
链表的图示

在下面的描述中,不再使用"存储空间"字眼,统一使用"节点"。

⑼、内部类Node的源码:

    //链表节点的类型
    //一个对象对应一个节点
    //包括元素(item)和节点之间的关系(prev,next)
    private static class Node<E> {
   
       //元素的引用
       //E表示可以是任何类型的元素
       //如果为null,表示没有存储任何元素
       //如果不为null,表示存储了某种类型的元素
       E item;
       //下一个节点的引用
       //熟悉Java的都知道,引用代表了
       //对象的十六进制地址值,所以
       //也可以注释为:下一个节点在内存中的地址
       //如果为null,可能是空链表
       //也可能是尾节点
       Node<E> next;
       //上一个节点的引用
       //如果为null,可能是空链表
       //也可能是首节点
       Node<E> prev;
       //构造方法
       //创建一个节点对象需同时提供:
       //①、元素的引用(item)
       //②、下一个节点的引用(next)
       //③、上一个节点的引用(prev)
       Node(Node<E> prev, E element, Node<E> next) {
   
          //元素的引用初始化
          this.item = element;
          //上一个节点的引用初始化
          this.next = next;
          //下一个节点的引用初始化
          this.prev = prev;
       }
    }

在接下来的源码分析中,节点都要处理元素关系两个方面。
在接下来的源码分析中,节点都要处理元素关系两个方面。
在接下来的源码分析中,节点都要处理元素关系两个方面。

1、size

//元素的数量,初始化为0
//关键字transient表示它不能被序列化
transient int size = 0;

2、first

//首节点的固定引用(方便节点的创建、添加和链表的查找遍历)
//链表必须先创建添加首节点,才能创建添加第二个节点,以此类推
//查找遍历同理,链表是顺序访问列表,只能从开头或者末尾开始挨个访问
//所以首节点不可能匿名,需要一个固定引用方便后续的使用
//关键字transient表示它不能被序列化
transient Node<E> first;

3、last

//尾节点的固定引用(方便节点的查找遍历)
//关键字transient表示它不能被序列化
transient Node<E> last;

4、LinkedList()

    //构造方法,创建一个没有任何节点的空LinkedList
    public LinkedList() {
   }

5、LinkedList(Collection<? extends E> c)

    //构造方法,创建这样一个LinkedList
    //①、节点数量与指定集合c的元素数量一致
    //②、指定集合c中的每个元素都存储在对应索引值的节点中
    //换句话说就是:节点的顺序与指定集合c的元素顺序完全一致
    public LinkedList(Collection<? extends E> c) {
   
       //调用无参构造函数,如果它在后期的Java
       //升级版本中发生修改,可以实时与它保持一致
       //保证了程序的可扩展性,方便了后期的代码维护
       this();
       //通过方法addAll(Collection<? extends E> c)
       //添加c的所有元素
       addAll(c);
    }

6、linkFirst(E e)

    //创建一个包含元素e的新节点
    //并链接至链表的首部(linkFirst),实际上
    //这个节点就是链表的新首节点
    //为什么需要这样一个private方法?
    //因为LinkedList同时implement了接口Queue和Deque
    //在实现它们的方法时可以有效避免代码重复 
    private void linkFirst(E e) {
   
       //假设链表不为空,获取并传递首节点的引用
       //传递后,引用f和first同时指向首节点
       //这一步实际上也是复制首节点的引用
       final Node<E> f = first;
       //分别提供以下参数创建一个节点对象newNode:
       //①、上一个节点的引用null
       //②、元素e
       //③、下一个节点的引用f
       //newNode其实就是新的首节点,两点原因:
       //①、在链表中,只有首节点的上一个节点引用为null
       //②、原首节点f成为newNode的下一个节点
       //③、节点关系的维护放在了构造函数中(参考截图)
       final Node<E> newNode = new Node<>(null, e, f);
       //将newNode赋值给first,保证first永远都是首节点的引用
       first = newNode;
       //---------------下面需要考虑一些其它情况
       //上面的代码只初始化了first,并未初始化last
       //如果首节点f为null,说明链表为空
       //所以新创建的节点newNode既是首节点也是尾节点
       if (f == null)
          //初始化last
          last = newNode;
       else
          //如果首节点f不为空
          //需要考虑两方面:元素和关系
          //元素由客户端程序员添加
          //这里只需考虑关系
          //首节点f的prev肯定为null
          //现在创建了新的首节点newNode
          //f自然就是第二节点
          //所以需要通过代码表示:
          //f的上一个节点是newNode
          //以此保证各个节点的关系正确
          f.prev = newNode;
       //新增一个节点,长度+1
       size++;
       //继承自抽象类AbstractList,保证快速失败机制的正常执行
       //新增节点是结构性修改
       modCount++;
    }

截图:
在这里插入图片描述
7、linkLast(E e)

    //创建一个包含元素e的新节点
    //并链接至链表的尾部(linkLast),实际上
    //这个节点就是链表的新尾节点
    //为什么需要这样一个方法?
    //因为LinkedList同时implement了接口Queue和Deque
    //在实现它们的方法时可以有效避免代码重复 
    void linkLast(E e) {
   
       //假设链表不为空,获取并传递尾节点的引用
       //传递后,引用l和last同时指向尾节点
       //这一步实际上也是复制尾节点的引用
       final Node<E> l = last;
       //分别提供以下参数创建一个节点对象newNode:
       //①、上一个节点的引用l
       //②、元素e
       //③、下一个节点的引用null
       //newNode其实就是新的尾节点,两点原因:
       //①、在链表中,只有尾节点的下一个节点引用为null
       //②、原尾节点l成为newNode的上一个节点
       //③、节点关系的维护放在了构造函数中(参考截图)
       final Node<E> newNode = new Node<>(l, e, null);
       //将newNode赋值给last,保证last永远都是尾节点的引用
       last = newNode;
       //---------------下面需要考虑一些其它情况
       //上面的代码只初始化了last,并未初始化first
       //如果尾节点l为null,说明链表为空
       //所以新创建的节点newNode既是尾节点也是首节点
       if (l == null)
          //初始化first
          first = newNode;
       else
          //如果尾节点l不为空
          //需要考虑两方面:元素和关系
          //元素由客户端程序员添加
          //这里只需考虑关系
          //尾节点l的next肯定为null
          //现在创建了新的尾节点newNode
          //l自然就是倒数第二节点
          //所以需要通过代码表示:
          //l的下一个节点是newNode
          //以此保证各个节点的关系正确
          l.next = newNode;
       //新增一个节点,长度+1
       size++;
       //继承自抽象类AbstractList,保证快速失败机制的正常执行
       //新增节点是结构性修改
       modCount++;
    }

截图:
在这里插入图片描述
8、linkBefore(E e, Node< E > succ)

    //创建一个包含指定元素e的节点
    //并将它链接在指定节点succ之前(linkBefore)
    void linkBefore(E e, Node<E> succ) {
   
        //获取节点succ的上一个节点
        final Node<E> pred = succ.prev;
        //分别提供以下参数创建一个节点对象newNode:
        //①、上一个节点的引用pred
        //②、元素e
        //③、下一个节点的引用succ
        final Node<E> newNode = new Node<>(pred, e, succ);
        //创建后还需要维护节点之间的关系
        //以保证newNode是succ的上一个节点
        succ.prev = newNode;
        //---------------下面需要考虑一些其它情况
        //如果pred为null,说明succ是链表的首节点
        //(只有首节点的上一个节点为null)
        //既然是首节点,那么就还有另一个固定的引用first
        if (pred == null)
           //newNode代替了succ成为新的首节点
           //需要对first重新进行初始化,保证first永远都是首节点的引用
           first 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值