Java基础之容器LinkedList

一、LinkedList的整体结构

1.1、LinkedList的继承关系

  • public class LinkedList<E> extends AbstractSequentialList <E> implements List<E>, Deque<E>
  • LinkedList具备AbstractSequentialList的特点:AbstractSequentialList 只支持按次序访问,而不像 AbstractList 那样支持随机访问
  • LinkedList具备List的特点
  • LinkedList具备Deque的特点:Deque是一个线性collection,支持在两端插入和移除元素

1.2、LinkedList的结构

//元素个数
transient int size = 0;
//第一个元素指针
transient Node<E> first;
//最后一个元素指针
transient Node<E> last;
//Node节点的结构
private static class Node<E> {
    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;
    }
}

1.2.1 Node的结构

LinkedList结构

LinkedList特点

1.LinkedList是通过双链表去实现的。

2.LinkedList不存在容量不足的问题,因为是链表。

3.LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问元素的方法,所以LinkedList可以作为FIFO(先进先出)的队列;LinkedList可以作为LIFO(后进先出)的栈

二、源码分析

2.1、添加元素

//添加元素
public boolean add(E e) {
    //默认调用,尾部添加元素的方法
    linkLast(e);
    return true;
}
//尾部添加元素
void linkLast(E e) {
    //记录当前尾部元素
    final Node<E> l = last;
    //创建一个新的Node节点 ,prev是当前的尾节点,next指向null
    final Node<E> newNode = new Node<>(l, e, null);
    //将last设置为新节点
    last = newNode;
    //判断当前尾部节点是否为null
    if (l == null)
        //当前尾部节点为null,就挂到头结点上
        first = newNode;
    else
        //当前尾部节点不为null,就将新建的Node挂到当前last节点的next指针上
        l.next = newNode;
    //元素的个数+1
    size++;
    //LinkedList修改记录+1
    modCount++;
}

新增元素add()方法默认是尾部追加,核心就是将新建的Node节点追加到当前last节点的next指针上 ,伪代码:

Node newNode=new Node();
newNode.prev=last;
last.next=newNode;
last=newNode;
last.next=null;

addFirst:首部追加

public void addFirst(E e) {
    linkFirst(e);
}
//头部追加
private void linkFirst(E e) {
    //记录当前首部元素
    final Node<E> f = first;
    //新建一个Node节点
    final Node<E> newNode = new Node<>(null, e, f);
    //首部元素指向新建的节点
    first = newNode;
    //判断当前首部指针是否为null
    if (f == null)
        //当前首部指针为null,就把新建的节点挂到last指针上
        last = newNode;
    else
        //当前首部指针不为null,就把新建的节点挂到,当前first指针指向元素的prev指针上
        f.prev = newNode;
    //元素个数+1
    size++;
    //LinkedList修改记录+1
    modCount++;
}

首部追加的逻辑与尾部追加基本相同,伪代码:

Node newNode=new Node();
newNode.next=first;
first.prev=newNode;
first=newNode;
first.prev=null;(也可以:newNode.prev=null)

指定位置添加元素:add(int index, E element):

public void add(int index, E element) {
    //检查要插入的位置是否合法
    checkPositionIndex(index);
    //如要插入的位置在最后,直接调用linkLast()
    if (index == size)
        linkLast(element);
    else
        //如要插入的位置不在最后,就先查找再插入
        linkBefore(element, node(index));
}

//查找要插入元素的位置
Node<E> node(int index) {
    // assert isElementIndex(index);
    //如果要插入的位置小于集合的一半,我就从头开始找
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        //如果要插入的位置大于等于集合的一半,我就从尾部开始找
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}
//将新建的元素插入到查找的元素前面
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

LinkedList是一个双向链表,他只记录了头部和尾部位置,如果我们要指定位置插入,他会这么做:

1.先遍历查找出要插入的元素位置,然后再插入;查找方式是根据 index < (size >> 1) 判断结果,决定是从头遍历,还是从尾部遍历,这种遍历方式类似于二分查找(只在第一层循环二分)

2.新建一个Node节点,插入到查找出来的元素的前面

由此可知为何链表对随机位置读写是不合适的;他的时间复杂度=O(n/2) ,如果n很大,我们一般就认为他的时间复杂度=O(n)

2.2、删除元素

//重写List的remove()
public boolean remove(Object o) {
    if (o == null) {
        //如果要删除的元素null元素,从头开始查找这个null元素
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
         //如果要删除的元素不null元素,从头开始查找这个非null元素
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}
//执行删除逻辑,实质就是打断改元素与链表的引用关系
E unlink(Node<E> x) {
    // assert x != null;
    //记录改元素的值,实际作用就是做返回值
    final E element = x.item;
    //记录当前元素的下一个节点
    final Node<E> next = x.next;
    //记录当前元素的上一个节点
    final Node<E> prev = x.prev;
    //判断 x->prev 节点是否为null,为null就是删除头结点
    if (prev == null) {
        first = next;
    } else {
        //将 x->prev节点的next指针指向x节点的下一个节点
        prev.next = next;
        //将 x->prev 指针,设置为null(断开引用关系)
        x.prev = null;
    }
     //判断 x->next 节点是否为null,为null就是删尾部结点
    if (next == null) {
        last = prev;
    } else {
        //将x->next节点的prev指针指向x->prev
        next.prev = prev;
        //将 x->next指针,设置为null(断开引用关系)
        x.next = null;
    }
    //将x的值设置为null
    x.item = null;
    //集合大小-1
    size--;
    //集合的修改记录-1
    modCount++;
    return element;
}

这里我们看到LinkedList重写了List的remove方法,整个删除逻辑也是先查找再删除,时间复杂度O(n),如果是删除首部元素时间复杂度=O(1),若要删除尾部元素请使用removeLast( )

  • LinkedLis删除首部元素:removeFirst()
  • LinkedLis删除尾部元素:removeLast()
  • LinkedLis首部出队:pollFirst( ) ,队列的特点
  • LinkedLit尾部出队:pollLast( ),队列的特点

2.3、迭代器

Iterator迭代器只能是从头往尾迭代,而LinkedList是双向链表,他还可以从尾往头部迭代,JAVA提供了一个新的迭代器接口:

public interface ListIterator<E> extends Iterator<E>{
    //判断是否存在下一个元素
    boolean hasNext();
    //获取下一个元素
    E next();
    //判断是否还有前一个元素
    boolean hasPrevious();
    //获取前一个元素
    E previous();
}

LinkedList实现该接口:

private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned;//上一次next() 或者 previous()的元素
    private Node<E> next;//lastReturned->next 指向的元素
    private int nextIndex;//下一个元素的位置
    private int expectedModCount = modCount;
}

LinkedList从前往后遍历:

//是否存在下一个元素
public boolean hasNext() {
    return nextIndex < size;
}
public E next() {
    //检查集合的版本
    checkForComodification();
    if (!hasNext())
        throw new NoSuchElementException();
    //lastReturned赋值上次next
    lastReturned = next;
    //next赋值为上次next->next
    next = next.next;
    //下一个元素的位置
    nextIndex++;
    return lastReturned.item;
}

LinkedList从后往前遍历:

//判断是否到头了
public boolean hasPrevious() {
    return nextIndex > 0;
}
//从尾部往头部取数据
public E previous() {
    checkForComodification();
    if (!hasPrevious())
        throw new NoSuchElementException();
    // next==null:第一次遍历取尾节点(last),或者上一次遍历时把尾节点删除掉了
    // next!=null:已经发生过遍历了,直接取前一个节点即可(next.prev)
    lastReturned = next = (next == null) ? last : next.prev;
    //遍历的指针-1
    nextIndex--;
    return lastReturned.item;
}

迭代器删除元素:

public void remove() {
    checkForComodification();
    // lastReturned 是本次迭代需要删除的值
    // lastReturned==null则调用者没有主动执行过 next() 或者 previos(),二直接调remove()
    // lastReturned!=null,是在上次执行 next() 或者 previos()方法时赋的值
    if (lastReturned == null)
        throw new IllegalStateException();
    //保存将当前要删除节点的下一个节点(如果是从尾往头遍历,该值永远是null)
    Node<E> lastNext = lastReturned.next;
    //删除当前节点
    unlink(lastReturned);

    // next == lastReturned:从尾到头递归顺序,并且是第一次迭代,并且要删除最后一个元素的情况下,
    // previous() 方法里面设置了 lastReturned = next = last,所以 next 和 lastReturned会相等
    if (next == lastReturned)
        next = lastNext;
    else
        nextIndex--;
    lastReturned = null;
    expectedModCount++;
}

三、总结

LinkedList底层数据结构是双向链表,所以他更适合顺序操作,由于他继承了Deque接口,同时他具有队列的性质,非线程安全的集合

到此这篇关于Java基础之容器LinkedList的文章就介绍到这了,更多相关Java容器LinkedList内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java基础之容器Vector详解

    一.前言 知识补充:Arrays.copyOf函数: public static int[] copyOf(int[] original, int newLength) { int[] copy = new int[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } 可见copyOf()在内部新建一个数组,调用arrayCopy()将ori

  • Java容器源码LinkedList原理解析

    LinkedList简介 LinkedList是一个使用双向链表结构实现的容器,与ArrayList一样,它能动态扩充其长度,LinkedList相较于ArrayList,其任意位置插入速度比ArrayList要快,但是其查询速度要比ArrayList要慢:LinkedList继承自AbstractSequentialList,实现了List.Deque.Cloneable.Serializable接口. LinkedList UML图如下: 和ArrayList一样,LinkedList也不是

  • 基于spring-boot和docker-java实现对docker容器的动态管理和监控功能[附完整源码下载]

    docker简介 Docker 是一个开源的应用容器引擎,和传统的虚拟机技术相比,Docker 容器性能开销极低,因此也广受开发者喜爱.随着基于docker的开发者越来越多,docker的镜像也原来越丰富,未来各种企业级的完整解决方案都可以直接通过下载镜像拿来即用.因此docker变得越来越重要. 本文目的 本文通过一个项目实例来介绍如果通过docker对外接口来实现对docker容器的管理和监控. 应用场景: 对服务器资源池通过docker进行统一管理,按需分配资源和创建容器,达到资源最大化利

  • Java通过工厂、Map容器创建对象的方法

    一.通过工厂+反射+配置文件创建对象 通过工厂+反射+配置文件获取对象 /** * @Author: Promsing * @Date: 2021/3/7 - 10:09 * @Description: 通过使用工厂+配置文件+反射实现创建对象 * @version: 1.0 */ public class AbsFactory { //声明一个变量(多例模式,每次通过工厂都会创建一个不同的实例) private static Object obj; public static Object c

  • Web容器启动过程中如何执行Java类

    1.监听(Listener) <!-- 配置监听 --> <listener> <listener-class>com.xian.jdbc.GetProperties</listener-class> </listener> package com.xian.jdbc; public class GetProperties{ } //implements ServletContextListener 可实现servlet的监听则启动中直接运行输出

  • Java如何使用Jetty实现嵌入式的Servlet容器

    最近在项目中遇到关于jetty的问题,所以在网上做一些科普,接下来就给大家做一些分享: Jetty是一个Java实现的开源的servlet容器,它既可以像Tomcat一样作为一个完整的Web服务器和Servlet容器,同时也可以嵌入在Java应用程序中,在Java程序中调用Jetty. Jetty下载地址,本文写作时的最新版本是9.1.2,下载jetty-distribution-9.1.2.v20140210.zip: 注意Jetty 9需要JDK 7,如果使用JDK 6的话会出现错误:jav

  • Java 如何从spring容器中获取注入的bean对象

    1.使用场景 控制层调用业务层时,控制层需要拿到业务层在spring容器中注入的对象 2.代码实现 import org.apache.struts2.ServletActionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.suppo

  • Java基础之容器LinkedList

    一.LinkedList的整体结构 1.1.LinkedList的继承关系 public class LinkedList<E> extends AbstractSequentialList <E> implements List<E>, Deque<E> LinkedList具备AbstractSequentialList的特点:AbstractSequentialList 只支持按次序访问,而不像 AbstractList 那样支持随机访问 Linked

  • Java基础之Spring5的核心之一IOC容器

    一.什么是IOC 1)控制反转,把创建对象和对象的调用过程交给Spring 管理. 2)使用IOC的目的,为了降低耦合度. 二.IOC的底层原理 XML解析.工厂模式.反射 三.IOC思想 基于IOC容器完成,IOC容器底层就是对象工厂. 四.Spring 提供IOC容器实现两种方式:(两个接口) (1)BeanFactory:IOC容器基本实现,是Spring内部的使用接口,不提供开发人员使用 特点:加载配置文件的时候不会创建对象,在获取(使用)对象才去创建. (2)ApplicationCo

  • Java基础入门 Swing中间容器的使用

    目录 Java基础入门 Swing中间容器 下面举例说明一下JScrollPane的方法 Java Swing顶层容器类 Swing拥有三个常用的顶层容器类 Java基础入门 Swing中间容器 在Swing中不仅有JFrame.JDialog这样的顶级窗口,还拥有一些中间容器,这些容器不能单独存在,必须依存在顶级窗口中.最常见的是JPanel.JScrollPane. JPanel:JPanel和AWT中的Panel组件使用方法基本一致,他是一个无边框不能被放大.移动.关闭的面板,它的默认布局

  • Java基础之集合框架详解

    一.前言 本节学习到的内容有以下5类,不分先后顺序: 集合Collection体系结构 List子类 与集合结合使用的迭代器对象 集合与数组的区别? 常见的一般数据结构整理 二.集合的由来? Collection List ArrayList Vector LinkedList Set hashSet treeSet 在集合没有出现之前,使用对象数组来存储对象,但是,对象数组的长度一旦确定,则不可以发生变化,所以我们希望存在一个容器就像StringBuffer一样存储字符串,同时依据传入的值的个

  • Java基础知识汇总

    Java基础知识 1.Java语言的优点: 1)Java是纯面向对象语言 2)与平台无关性,一次编译到处运行 3)Java提供了狠多内置类库 4)提供了对web应用的支持 5)具有较好的安全性(数组边界检测.Bytecode检测)和健壮性(强制型机制.垃圾回收器.异常处理) 6)去除c++难以理解的一些特性(头文件 指针 运算符重载 多重继承) 2.java与c++的异同: 1)Java为解释型语言,c++为编译型语言,java会慢但是跨平台 2)Jave为纯面向对象,c++既面向对象又能面向过

  • Java基础 Servlet监听器详解

    Java基础 Servlet监听器详解 1 概念:Servlet监听器,用来监听web容器的一些对象状态的变化,主要是ServletContext.HttpSession.HttpServletRequestl三类对象状态.Servlet的监听器 2  Servlet2.4和JSP2.0规范中一共定义了有八个接口类和六种事件. 3 web.xml中定义Servlet的url-pattern时如果url-pattern的值的"/",则说明该Servlet是该项目的默认Servlet,当请

  • JavaWeb基础教程之Java基础加强版

    1.myeclipse的安装和使用 * eclipse:是一个免费的开发工具 * myeclipse:是一个收费的插件,破解myeclipse, ** 安装目录的要求: 不能有中文和空格 ** 安装完成之后,选择一个工作空间 ,这个工作空间不能有中文和空格 * 破解myeclipse ** 运行run.bat文件,但是运行之前,必须要安装jdk,通过配置环境变量 * myeclipse的使用 * 创建一个工程 - 类型 java project web project - 选择依赖的jdk,可以

  • Java基础之Filter的实例详解

    Java基础之Filter的实例详解 定义: Filter,是Servlet的一种,接口类为javax.servlet.Filter,以一种模块化或者可重用的方法封装公共行为,本质是可复用的代码片段. 职责:在请求到达Servlet之前对请求头作预处理,或者在服务器响应完成之后对响应内容作后处理.分界线为chain.doFilter的调用.该调用是将请求处理权交给其Filter列表链上的其它Filter. 生命周期:  Filter在Web容器启动时被容器实例化,并调用其init方法完成初始化,

  • Java从同步容器到并发容器的操作过程

    引言 容器是Java基础类库中使用频率最高的一部分,Java集合包中提供了大量的容器类来帮组我们简化开发,我前面的文章中对Java集合包中的关键容器进行过一个系列的分析,但这些集合类都是非线程安全的,即在多线程的环境下,都需要其他额外的手段来保证数据的正确性,最简单的就是通过synchronized关键字将所有使用到非线程安全的容器代码全部同步执行.这种方式虽然可以达到线程安全的目的,但存在几个明显的问题:首先编码上存在一定的复杂性,相关的代码段都需要添加锁.其次这种一刀切的做法在高并发情况下性

随机推荐