iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java基础之容器LinkedList
  • 119
分享到

Java基础之容器LinkedList

2024-04-02 19:04:59 119人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

目录一、LinkedList的整体结构1.1、LinkedList的继承关系1.2、LinkedList的结构1.2.1 Node的结构 二、源码分析2.1、添加元素2.2

一、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内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java基础之容器LinkedList

本文链接: https://www.lsjlt.com/news/123971.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java基础之容器LinkedList
    目录一、LinkedList的整体结构1.1、LinkedList的继承关系1.2、LinkedList的结构1.2.1 Node的结构 二、源码分析2.1、添加元素2.2...
    99+
    2022-11-12
  • Java基础之容器Vector详解
    目录一、前言二、Vector简介三、Vector源码四、总结五、Vector遍历方式一、前言 知识补充:Arrays.copyOf函数: public static int[] ...
    99+
    2022-11-12
  • Java基础之Spring5的核心之一IOC容器
    目录一、什么是IOC二、IOC的底层原理三、IOC思想四、Spring 提供IOC容器实现两种方式:(两个接口)五、IOC操作之Bean管理一、什么是IOC 1)控制反转,把创建对象...
    99+
    2022-11-12
  • Java基础之ArrayList的扩容机制
    我们知道Java中的ArrayList对象底层是基于数组实现的,而数组是有长度限制的,那基于数组实现的ArrayList是否有长度限制呢?我们通过ArrayList的构造方法来剖析 ...
    99+
    2022-11-12
  • Python基础语法之容器详解
    目录Python基础语法-容器1.列表(list)1.1 列表基本概念1.2 获取元素1.3 增、删、改1.3.1 增 - —增加元素1.3.2 删 — 删除元素1.3.3 改—改变...
    99+
    2022-11-12
  • java基础之注解
    1、元注解1 @Target【作用】用于指定所标注的注解可以使用的位置,例如:@Target(ElementType.METHOD):表示可以使用在方法上,其他结构不能使用;@Target({ElementType.METHOD, Elem...
    99+
    2020-09-10
    java入门 java 注解
  • Java基础之Maven详解
    目录一、Maven环境的搭建1. 为什么要学习Maven?2. Maven项目架构管理工具3. 下载安装Maven4. 配置环境变量5. 阿里云...
    99+
    2022-11-12
  • Java基础之StringBuffer详解
    目录一、前言二、用法三、结果四、长度 容量五、IStringBuffer接口六、value和capacity一、前言 StringBuffer是可变长的字符串 1.append 追加...
    99+
    2022-11-12
  • Java基础之ClassLoader详解
    目录一、ClassLoader简介二、内置的CLassLoader的类型三、BootstrapClassLoader四、ExtClassLoader五、AppClassLoader六...
    99+
    2022-11-12
  • Java基础之FastJson详解
    目录一、fastJson将json格式字符串转化成List集合二、fastJson将json格式字符串转化成对象三、FastJson将对象或集合转化成json格式字符串四、FastJ...
    99+
    2022-11-12
  • Java基础之TreeMap详解
    目录一、写在前面二、定义三、成员变量四、内部类五、构造器六、成员方法一、写在前面 TreeMap的底层数据结构是红黑树,且TreeMap可以实现集合元素的排序。 所以TreeMap...
    99+
    2022-11-12
  • Java基础之初识Maven
    目录一、为什么使用Maven?二、使用Maven的好处三、Maven是什么?四、安装Maven五、第一个Maven六、Maven本地仓库的配置七、IDEA配置Maven八、第二个Ma...
    99+
    2022-11-12
  • Java基础之包装类
    目录一、java的包装类二、Integer包装类2.1Integer类的基本介绍2.2Integer类的属性2.3 Integer类的构造器三、自动装箱和自动拆箱四、Int...
    99+
    2022-11-12
  • Java 基础 之 while 循环
    转载于 : http://www.verejava.com/id=16992618818220 public class Test1 {public static voi...
    99+
    2023-06-02
  • Java基础之自定义类加载器
    目录一、类加载器关系二、基于本地class文件的自定义类加载器三、遇到的问题四、基于网络(url)class文件的自定义类加载器一、类加载器关系 自定义类加载器 创建一个类继承C...
    99+
    2022-11-12
  • Java ArrayList与LinkedList及HashMap容器怎么使用
    今天小编给大家分享一下Java ArrayList与LinkedList及HashMap容器怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所...
    99+
    2023-07-02
  • Java基础之CardLayout的使用
    目录一、案例介绍二、案例代码一、案例介绍 在编码前需要将本案例中使用到的三张图片(1.png 、2.png、3.png)保存到src所在的文件夹内。看下图: 1.png: 2.p...
    99+
    2022-11-12
  • 6. `Java` 并发基础之`ReentrantReadLock`
    前言:随着多线程程序的普及,线程同步的问题变得越来越常见。Java中提供了多种同步机制来确保线程安全,其中之一就是ReentrantLock。ReentrantLock是Java中比较常用的一种同...
    99+
    2023-09-21
    java 开发语言
  • java基础教程之接口
    定义:接口就是多个类的共有规范(里面的抽象方法),是一种引用数据类型。小提示:基本数据类型包括数值型(整数和浮点数)、字符型、布尔型。格式:public interface 接口名称{ //接口内容 }备注:接口.java编译后仍然是接口...
    99+
    2019-04-11
    java入门 java 接口
  • java基础之方法详解
    目录一、什么是方法二、方法的定义三、方法的调用四、方法的重载五、递归一、什么是方法 Java方法是语句的集合,他们在一起执行一个功能。 1.方法是解决一类问题的步骤的有序...
    99+
    2022-11-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作