Java LinkedList【笔记】

LinkedList

LinkedList 适用于要求有顺序,并且会按照顺序进行迭代的场景,依赖于底层的链表结构

LinkedList基本结构

LinkedList 底层数据结构是一个双向链表

链表每个节点叫做 Node,Node 有 prev 属性,代表前一个节点的位置,next 属性,代表后一个节点的位置

双向链表的头节点(first)的前一个节点是 null

双向链表的尾节点(last)的后一个节点是 null

当链表中没有数据时,first 和 last 是同一个节点,前后指向都是 null

因为是个双向链表,只要机器内存足够强大,是没有大小限制的

链表中的元素叫做 Node,初始化参数的顺序为前一个节点,本身节点值,后一个节点

LinkedList追加节点(新增节点)

在LinkedList追加节点的时候,可以选择追加到链表头部,也可以选择追加到链表尾部,add是默认为尾部追加addfirst是从头部追加

从头部开始追加(add)

简单来说,尾部追加节点只需要把指向位置修改下就行了

具体操作,我们首先需要将尾节点的数据暂时存储起来,然后建立一个新的节点,初始化,需要注意,新节点的前一个节点的值为尾节点值,新增节点的后一个节点为nul,然后将新建的节点追加到尾部,如果链表为空,头部和尾部都是同一个节点,都是新建的节点,否则把前尾节点的下一个节点指向当前尾节点

源码:

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++;
}
从头部追加的方法(addfirst)

将头节点赋值给临时变量,然后新建节点,前一个节点指向null,新建节点的下一个节点的值为头节点的值,然后将新建节点变成头节点,如果头节点为空,那么就是链表为空,头尾节点就是一个节点,此时上一个头节点的前一个节点就指向当前节点

源码:

private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}

通过这两个方法的对比,可以发现头部追加节点和尾部追加节点是非常相像的,前者是移动头节点的 prev 指向,后者是移动尾节点的next指向

LinkedList节点删除

和追加有一些类似,也是可以选择从头部删除或者是从尾部删除,删除的时候会把节点的值以及前后指向节点都变为null,这样有利于垃圾回收(GC)

从头部删除的话,首先我们先拿出头节点的值来作为方法的返回值,拿出头节点的下一个节点来,将前后设为null帮助垃圾回收,然后头节点的下一个节点成为头节点,如果next为空,则说明链表为空,如果链表不为空,则将头节点的下一个节点指向null,然后修改一下链表大小

源码:

private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null;
first = next;
if (next == null)
last = null;
null
else
next.prev = null;
size--;
modCount++;
return element;
}

可以发现,链表结构的节点新增和删除都只需要将前后节点的指向修改下就可以,这说明LinkedList的新增和删除速度很快

LinkedList节点查询

链表的查询是比较慢的,而在LinkedList中进行节点查询不是按照从头循环到尾的方法,而是用的简单二分法,先看index是在链表的前半部分还是后半部分,如果是前半边,就从头找,如果是后半边,就从尾找,这样可以提高一些性能,而且因为链表的性质,即只能从第一个或者最后一个去访问其他的元素,所以简单二分法已经是最优解了

源码:

Node<E> node(int 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;
}
}

LinkedList中的接口的新增方法

LinkedList迭代器

LinkedList使用ListIterator迭代接口来实现双向的迭代访问,这个接口提供了向前以及向后的迭代方法

从头到尾的方向的迭代

首先判断一下有没有下一个元素,如果下一个节点的索引小于链表的大小,那么就说明有下一个元素,然后我们取一个元素,看一下版本号有无变化,然后再检查一遍,next是当前的节点,在上一次执行next()方法的时候被赋值的,如果是第一次执行,那么就是在初始化迭代器的时候被赋值的,然后next是下一个节点,为下一次的迭代做准备

源码:

public boolean hasNext() {
return nextIndex < size;
} public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
从尾到头的迭代

首先我们要确定上次节点的索引位置,如果上次节点索引位置大于0,那么就代表有节点可以迭代,然后我们取前一个节点,同样检查版本号,在next为空的时候,有两个可能,第一个,说明这是第一次迭代,取尾节点,第二个,上次操作的时候把尾节点删掉了,而在next不为空的时候,这就说明已经发生过迭代,直接去前一个节点就可以了,然后改变索引位置

源码:

public boolean hasPrevious() {
return nextIndex > 0;
} public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
(next.prev)
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}

一些问题:

ArrayList 和 LinkedList 有何异同?

不同:

底层数据结构方面

ArrayList 底层是数组

LinkedList 底层是双向链表

应用场景方面

ArrayList 更适合于快速的查找匹配,不适合频繁新增删除

LinkedList 更适合于经常新增和删除,对查询反而很少的场景

相同:

最大容量

ArrayList 有最大容量的,为 Integer 的最大值,大于这个值 JVM 是不会为数组分配内存空间的

LinkedList 底层是双向链表,理论上可以无限大,但是实际上,LinkedList 实际大小用的是 int 类型,这也说明了 LinkedList 不能超过 Integer 的最大值,不然会溢出

** null 值**

ArrayList 允许 null 值新增,也允许 null 值删除

LinkedList 新增删除时对 null 值没有特殊校验,是允许新增和删除的。

线程安全

当两者作为非共享变量时,比如说仅仅是在方法里面的局部变量时,是没有线程安全问题的,只有当两者是共享变量时,才会有线程安全问题

Java LinkedList【笔记】的更多相关文章

  1. Java开发笔记(六十七)清单:ArrayList和LinkedList

    前面介绍了集合与映射两类容器,它们的共同特点是每个元素都是唯一的,并且采用二叉树方式的类型还自带有序性.然而这两个特点也存在弊端:其一,为啥内部元素必须是唯一的呢?像手机店卖出了两部Mate20,虽然 ...

  2. 《Java学习笔记(第8版)》学习指导

    <Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...

  3. 20145330第五周《Java学习笔记》

    20145330第五周<Java学习笔记> 这一周又是紧张的一周. 语法与继承架构 Java中所有错误都会打包为对象可以尝试try.catch代表错误的对象后做一些处理. 使用try.ca ...

  4. Java学习笔记4

    Java学习笔记4 1. JDK.JRE和JVM分别是什么,区别是什么? 答: ①.JDK 是整个Java的核心,包括了Java运行环境.Java工具和Java基础类库. ②.JRE(Java Run ...

  5. Java开发笔记(序)章节目录

    现将本博客的Java学习文章整理成以下笔记目录,方便查阅. 第一章 初识JavaJava开发笔记(一)第一个Java程序Java开发笔记(二)Java工程的帝国区划Java开发笔记(三)Java帝国的 ...

  6. Java开发笔记(七十)Java8新增的几种泛型接口

    由于泛型存在某种不确定的类型,因此很少直接运用于拿来即用的泛型类,它更经常以泛型接口的面目出现.例如几种基本的容器类型Set.Map.List都被定义为接口interface,像HashSet.Tre ...

  7. JAVA自学笔记15

    JAVA自学笔记15 @例题1:共有5个学生,请把五个学生的信息存储到数组中,并遍历数组,并获取每个学生的信息 Students[] students=new Student[5]; Student ...

  8. JAVA自学笔记19

    JAVA自学笔记19 1.集合总结 Collection(单列集合) List(有序可重复) ArrayList:底层数据结构是数组 ,查询快,增删慢.线程不安全,效率高 Vector:底层数据结构是 ...

  9. JAVA自学笔记17

    JAVA自学笔记17 1.Map接口 1)概述 将键映射到值的对象,一个映射不能包含重复的键,每个键最多只能映射到一个值.可以存储键值对的元素 2)与Collection接口的不同: ①Map是双列的 ...

随机推荐

  1. 一道内部ctf文件包含题

    拿到题目 在burp里看下 拿到源码 很明显是一道文件包含题目,包含cookie里的值,于是构造Cookie:language=chinese试试   文件变成中文的了,说明中文语言进行了包含并替换 ...

  2. java二叉树的遍历(1)

    树(tree)是一种抽象数据类型(ADT),用来模拟具有树状结构性质的数据集合.它是由n(n>0)个有限节点通过连接它们的边组成一个具有层次关系的集合 节点:上图的圆圈,比如A,B,C等都是表示 ...

  3. 没事就要多做多练,Shell脚本循环例题做一做

    Shell脚本循环例题                 一.示例1                 二.示例2                 三.示例3                 四.示例4 ...

  4. etcd的使用

    etcd的使用 什么是etcd etcd的特点 etcd的应用场景 服务注册与发现 消息发布和订阅 负载均衡 分布式通知与协调 分布式锁 分布式队列 集群监控与Leader竞选 参考 etcd的使用 ...

  5. 龙芯 loongnix20 rc2 初体验

    2021-07-24 v0.0.1 版权声明:原创文章,未经博主允许不得转载 3A5000 昨天发布啦,历史上的昨天是中共一大的第一天. 3A5000 的团购还没开始(大概还是3999左右整机的样子) ...

  6. 【游记】OI 2020-2021(在更)

    [CSP-S2020初赛] [CSP-S2020] [NOIp 2020] [NOI冬令营 2021] [省选 2021] [NOI 2021]

  7. vue3源码难学,先从petite-vue开始吧

    如今这个世道,作为一个有几年工作经验的前端,不学点框架源码都感觉要被抛弃了,react或vue要能吹吹牛吧,最好能造个轮子,听说vue3源码好学点,那么学学vue3,但是学起来还是那么费劲,感觉快放弃 ...

  8. 新一代垃圾回收器ZGC的探索与实践

    ZGC(The Z Garbage Collector)是JDK 11中推出的一款低延迟垃圾回收器,它的设计目标包括: 停顿时间不超过10ms: 停顿时间不会随着堆的大小,或者活跃对象的大小而增加: ...

  9. js 调用json

    url = "/plus/API/"; try { // 此处是可能产生例外的语句 } catch(error) { // 此处是负责例外处理的语句 } finally { // ...

  10. 就这?Spring 事务失效场景及解决方案

    小明:靓仔,我最近遇到了很邪门的事. 靓仔:哦?说来听听. 小明:上次看了你的文章<就这?一篇文章让你读懂 Spring 事务>,对事务有了详细的了解,但是在项目中还是遇到了问题,明明加了 ...