一文彻底带你搞清楚LinkedList,通俗易懂

Posted 庆哥Java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文彻底带你搞清楚LinkedList,通俗易懂相关的知识,希望对你有一定的参考价值。

说到LinkedList那就必然会想到ArrayList,很多人也会将两者进行比较,的确,我们知道在数据结构中,数组和链表称得上是数据结构中的数据结构,也就是说很多高级的数据结构都是在数组或者链表以及两者的基础之上建立而成!

对于ArrayLsit而言,它的底层使用的是数组这一数据结构,相对应的,LinkedList则是使用链表作为底层数据结构,而且还是一个双向链表!

也就是对于ArrayList而言,它的底层是这样的数组:

而对于LinkedList而言,底层则是这样的一个双向链表:

那基于链表的一些特性,我们就能知道,LinkedList就可以进行高效的插入和删除操作!

基本使用

首先,我们来看LinkedList的基本使用:

LinkedList ls = new LinkedList();
ls.add("hello");
ls.add(1);

LinkedList<String> lts = new LinkedList<>();
lts.add("hello");

这一点其实是和ArrayList一样的,就是当你不指定类型的时候,可以存储任意类型的字段,一旦指定类型,就只能存储特定类型的数据了!

接着我们看下LinkedList的继承体系:

可以看到,对于LinkedList来说,不仅实现了List接口,而且是实现了Deque接口,这是啥?队列,所以对于LinkedList来说既可以是一个List也可以是一个队列!

无参构造函数

我们从无参构造函数来看:

啥也没有,就是一个空的方法,那么当我们去添加一个元素呢?

public boolean add(E e) 
    linkLast(e);
    return true;

以上就是添加一个元素的方法,这里我们需要重点关注的就是这个"linkLast(e)"了,看看它是啥?

这里对于方法有一句注释:

Links e as last element.

直白的翻译一下,就是把添加的元素放在链表的最后,因为对于LinkedList而言,底层就是一个链表,而且是一个双向链表,再看下这个图:

也就是说,LinkedList底层的结构就是上面这么一个双向链表,当我们往这个链表中添加一个元素的时候,默认是添加在这个链表的末尾,所以你看这个方法:

“Last”也就是最后的意思,这个时候我们需要主要分析的就是这个添加元素的方法:

/**
     * Links e as last element.
     */
    void linkLast(E e) 
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    

添加元素

看到这里,必须提一句,要想比较好的理解LinkedList,就需要你对基本的数据结构链表有一个比较清楚的认识,比如链表中的节点,你要知道怎么回事,如果还不清楚的,可以先去学习链表这种数据结构,这样你才能更好的学习LinkedList!

然后我们再来看这个添加元素的方法,首先要看的就是以下两句代码:

final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);

Node节点分析

看到这些,比较直观的就是这个Node,那这是什么呢?如果你熟悉链表,马上就能知道,这就是组成底层链表的那个节点,我们来看在LinkedList中这个节点长什么样:

这个Node节点该怎么解读?

对于一个节点而言,它的组成不单单是一个数据那么简单,除了数据,还有相对应的指针,这里的指针指的就是指向前一个节点和后一个节点的指针,当然是对于双向链表而言!

而这个指针是啥,其实就是一个Node节点的引用,通过这个Node节点引用可以找到具体的一个Node节点,再看这个结构:

也就是说对于一个双向链表中节点而言,基本包括是那个部分:

  1. 数据本身
  2. 前指针
  3. 后指针

然后你再看这里的Node节点:

这个时候是不是对于以下代码就比较熟悉了:

E item;
Node<E> next;
Node<E> prev;

然后再看这段代码:

Node(Node<E> prev, E element, Node<E> next) 
            this.item = element;
            this.next = next;
            this.prev = prev;
        

这个就是对于一个Node节点而言的有参构造,也就是当你创建一个Node节点的时候需要为其指定相应的数据和对应的指针!

以上就是对于LinkedList中Node节点的分析!

主要元素

了解了节点之后,我们再看在LinkedList中定义的三个元素值:

在了解节点之后,这些看起来就都不是问题,然后我们再看添加元素的这些代码:

首先是这两行:

final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);

第一行也就是先创建一个最基础的默认的链表尾节点,也就是提前声明的last,然后是第二行代码,这里创建了一个“newNode”,见名知意,一个新的节点,这个时候需要再看下Node节点的构造函数:

注意构造函数中需要传入的参数:

  1. 一个前指针,也就是指向前面的一个节点
  2. 当前节点数据
  3. 一个后指针,也就是指向后面的一个节点

然后再看这句代码:

final Node<E> newNode = new Node<>(l, e, null);	

这里创建一个新的节点,可以想下,此时添加第一个元素,LinkedList底层链表并没有已存在的节点,所以这里创建的新节点将作为第一个节点存在,而第一个节点是没有后置节点的,因此后置节点为null,虽然这里前置节点是“l”,但是要知道此时这个l也是只有一个声明,没有具体的指向,再看这里:

所以经过以上步骤,我们就得到了该LinkedList中的第一个节点,且数据为传入的“e”,紧接着就是判断了,因此插入的是第一个元素,此时的l没有指向,为null,因此“first == newNode”,这个很好理解,因为当只有一个节点的时候,这个节点可以说是最后一个也可以说是第一个!

以上是插入的是第一个元素的时候的情况,如果我们再插入一个元素的话,同样会走这个“linkLast”方法,要知道,LinkedList的插入是在链表的末尾插入,画个图就是这样:

也就是说,当第一次添加一个元素的时候,此时就只有这一个元素,所以该元素即是第一个也可以是最后一个,当添加第二个元素的时候,原来的那个元素就要往前挪,而新插入的这个元素就成了最后一个元素,也就是成了“last”,而之前的那个元素的next就会指向新插入的这个元素,而新插入的这个元素的pre就会指向之前的那个元素,然后再对照代码,仔细理解:

当你添加第三个,第四个……数据的时候,其实逻辑就喝添加第二个是一样的了!

这就是LinkedList的添加操作!

删除元素

接下来我们看删除操作,基本使用如下:

也就是对于删除来说,有两个,一个是根据指定的位置进行删除,一个是删除指定的元素,那这个删除是如何操作的呢?我们先看这个:

也就是删除指定的元素,首先你进行判断,看看你删除的这个是否为null:

不为null的话则通过equals方法找到要删除的这元素的引用:

找到这个引用之后则通过以下方法将引用修改来达到删除的目的:

接着再看另一种:

同样是先通过该方法找到引用:

最终调用如下方法删除:

获取元素

接着看LinkedList的元素获取,使用如下:

我们主要看如下通过位置获取的方法:

这个获取主要是依靠node(int index)方法实现。详细的可以自己阅读完整源码了解!

设置元素

接下来看设置元素,如下使用:

方法将指定下标处的元素修改成指定值,也是先通过node(int index)找到对应下表元素的引用,然后修改Node中item的值。

如下源码:

总结

ArrayList和LinkedList的区别、优缺点以及应用场景

区别:

ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表结构。

对于随机访问的get和set方法查询元素,ArrayList要优于LinkedList,因为LinkedList循环链表寻找元素。

对于新增和删除操作add和remove,LinkedList比较高效,因为ArrayList要移动数据。

优缺点:

对ArrayList和LinkedList而言,在末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是 统一的,分配一个内部Entry对象。

在ArrayList集合中添加或者删除一个元素时,当前的列表移动元素后面所有的元素都会被移动。而LinkedList集合中添加或者删除一个元素的开销是固定的。

LinkedList集合不支持 高效的随机随机访问(RandomAccess),因为可能产生二次项的行为。

ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间

应用场景:

ArrayList使用在查询比较多,但是插入和删除比较少的情况,而LinkedList用在查询比较少而插入删除比较多的情况

以上是关于一文彻底带你搞清楚LinkedList,通俗易懂的主要内容,如果未能解决你的问题,请参考以下文章

一文彻底带你搞清楚LinkedList,通俗易懂

一文彻底带你搞清楚LinkedList,通俗易懂

一文彻底搞清Gradle依赖

带你一文搞清 Gradle 依赖,纯干货汇总!

通俗易懂,一文彻底理解Java内存结构中的方法区,你绝对可以看得懂

通俗易懂,一文彻底理解Java内存结构中的方法区,你绝对可以看得懂