热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JAVA容器之LinkedList原创

原标题:JAVA容器之LinkedList原创一、LinkedList的整体结构1、L

原标题:JAVA容器之LinkedList
原创

一、LinkedList的整体结构


1、LinkedList的继承关系:



  • public class LinkedList extends AbstractSequentialList implements List, Deque

  • LinkedList具备AbstractSequentialList的特点:AbstractSequentialList 只支持按次序访问,而不像 AbstractList 那样支持随机访问

  • LinkedList具备List的特点:

  • LinkedList具备Deque的特点:Deque是一个线性collection,支持在两端插入和移除元素


2、LinkedList的结构

//元素个数
transient int size = 0;
//第一个元素指针
transient Node first;
//最后一个元素指针
transient Node last;
//Node节点的结构
private static class Node {
E item;//当前元素
Node next;//当前元素的下一个指针
Node prev;//当前元素的上一个指针
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}

Node的结构:

LinkedList结构:


LinkedList特点:


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

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

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


二、源码分析


1、添加元素

//添加元素
public boolean add(E e) {
//默认调用,尾部添加元素的方法
linkLas文章来源地址40526.htmlt(e);
return true;
}
//尾部添加元素
void linkLast(E e) {
//记录当前尾部元素
final Node l = last;
//创建一个新的Node节点 ,prev是当前的尾节点,next指向null
final Node newNode = new Node<>(l, e, null);
//将last设置为新节点
last = newNode;
//判断当前尾部节点是否为null
if (l == null)
//当前尾部节点为null,就挂到头结点上
first = newNode;
www.yii666.comelse
//当前尾部节点不为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 f = first;
//新建一个Node节点
final Node 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 node(int index) {
// assert isElementIndex(index);
//如果要插入的位置小于集合的一半,我就从头开始找
if (index <(size >> 1)) {
Node x = first;
for (int i = 0; i x = x.next;
return x;
} else {
//如果要插入的位置大于等于集合的一半,我就从尾部开始找
Node x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//将新建的元素插入到查找的元素前面
void linkBefore(E e, Node succ) {
// assert succ != null;
final Node pred = succ.prev;
final Node 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文章来源地址40526.html、删除元素

//重写List的remove()
public boolean remove(Object o) {
if (o == null) {
//如果要删除的元素null元素,从头开始查找这个null元素
for (Node x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
//如果要删除的元素不null元素,从头开始查找这个非null元素
for (Node x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
//执行删除逻辑,实质就是打断改元素与链表的引用关系
E unlink(Node x) {
// assert x != null;
//记录改元素的值,实际作用就是做返回值
final E element = x.item;
//记录当前元素的下一个节点
final Node next = x.next;
//记录当前元素的上一个节点
final Node prev = x.prev;
//判断 x->prev 节点是否为null,为null就是删除头结点
if (prev == null) {
first = next;
} else {
//将 x->prev节点的next指针指向x节点的下一个节点
prev.next = next;
//将 x->prev 指针,设置为null(断开引用关系文章来源站点https://www.yii666.com/)
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( ),队列的特点


3、迭代器

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

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

LinkedList实现该接口:

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

LinkedList从前往后遍历:

//是否存在下一个元素
public boolean hasNext() {
return nextIndex }
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;
//www.yii666.com遍历的指针-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 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的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了Java的公式汇总及相关知识,包括定义变量的语法格式、类型转换公式、三元表达式、定义新的实例的格式、引用类型的方法以及数组静态初始化等内容。希望对读者有一定的参考价值。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
author-avatar
阳光明媚-在路上
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有