LinkedList定义

LinkedList 是链表实现的线性表(双链表),元素有序且可以重复。

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable

蓝色实线箭头是指Class继承关系

绿色实线箭头是指interface继承关系

绿色虚线箭头是指接口实现关系

由上可知LinkedList继承AbstractSequentialList并且实现了List和Deque,Cloneable, Serializable接口。

、实现 List 接口

  List 接口定义了实现该接口的类都必须要实现的一组方法,如下所示,下面我们会对这一系列方法的实现做详细介绍。

字段属性

//链表元素(节点)的个数
transient int size = 0;
//第一个元素的指针
transient Node<E> first;
//最后一个元素的指针
transient Node<E> last;

注意这里出现了一个 Node 类,这是 LinkedList 类中的一个内部类,集合里每一个元素就代表一个 Node 类对象,LinkedList 集合就是由许多个 Node 对象类似于手拉着手构成,由此可知,LinkedList是一个双向链表。

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;
}
}

如图所示:每个节点都prev都保存上一个节点的引用,next保存下一个节点的引用;

  需要注意:第一个节点prev没有指向的节点,为null,最后一个节点next也没有指向的节点,也为null

构造函数

①、无参构造函数

public LinkedList() {
}

注意:这里不需要初始化链表的大小,不像ArrayList,需要初始化数组的大小才能添加元素

②、泛型参数有参构造函数

public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}

将已有元素的集合Collection 的实例添加到 LinkedList 中,下面详细介绍

添加元素

①、add(E e)

public boolean add(E e) {
//将元素添加到链表的尾部
linkLast(e);
return true;
} void linkLast(E e) {
final Node<E> l = last;//先用变量存储链表尾部节点
final Node<E> newNode = new Node<>(l, e, null);//新添加一个元素,需要新增一个节点,元素内容为e,prev为当前last节点的地址引用,next为null
last = newNode;//将链表尾部指向新节点
if (l == null)
first = newNode;//如果链表为空,将这个新节点也设为头部节点,那么新增节点既是头节点也是尾节点,并且头节点的prev和next都是null
else
l.next = newNode;//链表不为空,将之前存储的原链表尾部节点的next指向新增节点
size++;//节点数加1
modCount++;//和ArrayList中一样,防止集合遍历时做元素的添加
}

此时需要注意,如果链表为空时,第一个元素的指针和最后一个元素的指针都指向当前节点

②、addFirst(E e)

public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first;//先用变量 f 存储链表头部节点
final Node<E> newNode = new Node<>(null, e, f);//将指定元素构造成一个新节点,元素内容为e,prev为null,next为当前first节点的地址引用,
first = newNode;//将链表头部指向新节点
if (f == null)
last = newNode;//如果链表为空,将这个新节点也设为尾节点,那么新增节点既是头节点也是尾节点
else
f.prev = newNode;//如果链表不为空,讲原链表的头部节点 f 的上一个节点指向新节点
size++;//节点数加1
modCount++;//和ArrayList中一样,防止集合遍历时做元素的添加
}

③、addLast(E e)

public void addLast(E e) {
linkLast(e);
}

和add(E e) 实现相同,在链表尾部添加元素

④、public void add(int index, E element)

  将元素插入指定位置

public void add(int index, E element) {
//判断索引是否越界 return index >= 0 && index <= size;
checkPositionIndex(index);
if (index == size)//如果索引值等于链表大小
linkLast(element);//将节点直接插入链表尾部;上面已经分析过
else
linkBefore(element, node(index));
} //根据索引获取节点,因为是链表,不像数组,在内存中并不是一块连续的位置存储,不能根据索引直接取到值,需要从头部或者尾部一个个向下找
Node<E> node(int index) {
//size >> 1表示移位,指除以2的1次方
if (index < (size >> 1)) {//如果索引比链表的一半小
Node<E> x = first;//设x为头节点,表示从头节点开始遍历
for (int i = 0; i < index; i++)//因为只需要找到index处,所以遍历到index处就可以停止
x = x.next;//从第一个节点开始向后移动,直到移动到index前一个节点就能找到index处的节点
return x;
} else {//如果索引比链表的一半大
Node<E> x = last;//设x为尾部节点,表示从最后一格节点开始遍历
for (int i = size - 1; i > index; i--)
x = x.prev;//从最后一个节点开始向前移动,直到移动到index后一个节点就能找到index处的节点
return x;
}
} void linkBefore(E e, Node<E> succ) {
final Node<E> pred = succ.prev;//获取index 节点的上一个节点
final Node<E> newNode = new Node<>(pred, e, succ);//构造新插入的节点,新节点pred设为原链表index的上一个节点,next处设为原始链表的index处的节点
succ.prev = newNode;//原始index处的节点的上一个几点引用设为新节点
if (pred == null)//如果插入节点的上一个节点引用为空
first = newNode;//头部节点设为新节点
else
pred.next = newNode;//原始index的上一个节点的next 设为新节点
size++;
modCount++;
}

我们发现LinkedList 每次添加元素只是改变元素的上一个指针引用和下一个指针引用,而且没有扩容。对比于 ArrayList ,需要扩容,而且在中间插入元素时,后面的所有元素都要移动一位,两者插入元素时的效率差异很大

查找元素

①、get(int index)

public E get(int index) {
//判断索引是否越界 return index >= 0 && index <= size;
checkElementIndex(index);
//node(index)上面已经讲过,这里获取节点的实际元素
return node(index).item;
}

②、indexOf(Object o)

返回链表中第一个出现指定元素的索引

public int indexOf(Object o) {
int index = 0;
if (o == null) {//查找的元素为null
for (Node<E> x = first; x != null; x = x.next) {//从头结点开始不断向下一个节点进行遍历
if (x.item == null)
return index;//找到了则返回index
index++;
}
} else {//查找的元素不为null
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))//使用equals 比较
return index;//找到了则返回index
index++;
}
}
return -1;//找不到指定元素,则返回-1
}

修改元素

public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);//获取指定索引处的节点
E oldVal = x.item;//获取指定索引处节点的实际元素
x.item = element;//将指定位置的节点元素替换成需要修改的元素
return oldVal;//返回索引处原来的节点的元素值
}

删除元素

①、通过索引位置删除

public E remove(int index) {
checkElementIndex(index);
//先获取指定位置的节点
return unlink(node(index));
} E unlink(Node<E> x) {
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev; if (prev == null) {//如果删除节点的上一个节点引用为null,表示删除节点为头节点
first = next;//将头节点设置为删除节点的下一个节点
} else {
prev.next = next;//将删除节点的上一个节点的next指向删除节点的下一个节点
x.prev = null;//将删除节点的上一个节点引用设为null,否则链表就乱了
} if (next == null) {//如果删除节点的下一个节点引用为null,表示删除节点为尾部节点
last = prev;//将尾部节点设为删除节点的上一个节点
} else {//不是尾部节点
next.prev = prev;//将删除节点的下一个节点的prev指向删除节点的上一个节点
x.next = null;//将删除节点的下一个节点引用设为null
} x.item = null;//将删除节点的实际元素设为null,便于垃圾回收
size--;
modCount++;
return element;
}

与ArrayList比较而言,LinkedList的删除动作不需要“移动”很多数据,从而效率更高

总结

ArrayList和LinkedList在性能上各有优缺点:

1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,偶尔可能会导致对数组扩容;而对LinkedList而言,这个开销是统一的,都是新建一个Node对象节点。
2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的,只需要改变元素邻近节点的上下引用地址。
3.LinkedList不支持高效的随机元素访问,因为需要从第一个元素或者最后一个元素开始遍历查找;ArrayList可以直接根据索引取到对应位置的元素。
4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间,每个元素不仅保存了当前元素的实际内容,还有上一个节点和下一个节点的引用

所以在我们进行对元素的增删查操作的时候,进行查操作时用ArrayList,进行增删操作的时候最好用LinkedList。

JDK1.8源码(二)——java.util.LinkedList的更多相关文章

  1. JDK1.8源码(六)——java.util.LinkedList 类

    上一篇博客我们介绍了List集合的一种典型实现 ArrayList,我们知道 ArrayList 是由数组构成的,本篇博客我们介绍 List 集合的另一种典型实现 LinkedList,这是一个有链表 ...

  2. JDK1.8源码(五)——java.util.Vector类

    JDK1.8源码(五)--java.lang. https://www.cnblogs.com/IT-CPC/p/10897559.html

  3. JDK1.8源码(七)——java.util.HashMap 类

    本篇博客我们来介绍在 JDK1.8 中 HashMap 的源码实现,这也是最常用的一个集合.但是在介绍 HashMap 之前,我们先介绍什么是 Hash表. 1.哈希表 Hash表也称为散列表,也有直 ...

  4. JDK1.8源码(四)——java.util.Arrays类

    一.概述 1.介绍 Arrays 类是 JDK1.2 提供的一个工具类,提供处理数组的各种方法,基本上都是静态方法,能直接通过类名Arrays调用. 二.类源码 1.asList()方法 将一个泛型数 ...

  5. JDK1.8源码(九)——java.util.LinkedHashMap 类

    前面我们介绍了 Map 集合的一种典型实现 HashMap ,关于 HashMap 的特性,我们再来复习一遍: ①.基于JDK1.8的HashMap是由数组+链表+红黑树组成,相对于早期版本的 JDK ...

  6. JDK1.8源码(二)——java.lang.Integer类

    一.初识 1.介绍 int 是Java八大基本数据类型之一,占据 4 个字节,范围是 -2^31~2^31 - 1,即 -2147483648~2147483647.而 Integer 是 int 包 ...

  7. JDK1.8源码(二)——java.lang.Integer 类

    上一篇博客我们介绍了 java.lang 包下的 Object 类,那么本篇博客接着介绍该包下的另一个类 Integer.在前面 浅谈 Integer 类 博客中我们主要介绍了 Integer 类 和 ...

  8. JDK1.8源码(四)——java.util.Arrays 类

    java.util.Arrays 类是 JDK 提供的一个工具类,用来处理数组的各种方法,而且每个方法基本上都是静态方法,能直接通过类名Arrays调用. 1.asList public static ...

  9. JDK1.8源码(五)——java.util.ArrayList 类

    关于 JDK 的集合类的整体介绍可以看这张图,本篇博客我们不系统的介绍整个集合的构造,重点是介绍 ArrayList 类是如何实现的. 1.ArrayList 定义 ArrayList 是一个用数组实 ...

随机推荐

  1. SpringBoot与日志框架1(基本使用)

    一.日志框架 1.无论在什么系统,日志框架都是一个重要角色,所以理解和用好日志框架是相当重要的:像JDBC一样,日志框架分为接口层的门面和具体的实现组成. 2.市面上的产品: 2.1门面:SLF4J( ...

  2. 通过zabbix的API接口获取服务器列表

    Zabbix API说明 1) 基于Web的API,作为Web前端的一部分提供,使用JSON-RPC 2.0协议 2) 身份认证Token:在访问Zabbix中的任何数据之前,需要登录并获取身份验证令 ...

  3. ASP.NET Aries 开发框架(已支持.NET Core)

    背景: 当年,在卖弄与开源QBlog时,也曾想过把QBlog的开发理念整理整理,独立一个框架来开源. 不过,人越长大就越憔悴,激情终敌不过疲惫的惰性,最终无痕而终,连3.0的版本也没开源出来. 关于框 ...

  4. git 的常用命令(未完待补充)

    一.初始化 git git init 这样会默认创建 master 分支 二.查看当前状态 git status  查看 git 的默认状态 三.创建一个文件,并把它添加到 git 仓库,使用 git ...

  5. LocalDate、LocalDateTime、LocalTime开发小结

    在我之前的文章<[整理]Java 8新特性总结 >中有提到Date/Time API (JSR 310)对日期与时间的处理.它将服务端对时间的处理进行了统一,使得对时间的处理更加规范和统一 ...

  6. Exp2 后门原理与实践 20164302 王一帆

    1 实验内容 1.1实验主要内容 (1)使用netcat获取主机操作Shell,cron启动 (0.5分) (2)使用socat获取主机操作Shell, 任务计划启动 (0.5分) (3)使用MSF ...

  7. AES加密算法详解

    AES 是一个对称密码分组算法,分组长度为128bit,密钥长度为128.192 和 256 bit. 整个加密过程如下图所示. 1.密钥生成算法 密钥扩展过程: 1)  将种子密钥按下图所示的格式排 ...

  8. delegate异步

    using System; using System.Runtime.Remoting.Messaging; using System.Threading; using System.Threadin ...

  9. Mac软件安装提示程序已损坏解决方案

    ---恢复内容开始--- 最近下载好的Mac软件安装时,系统跳出该程序已损坏: 嗯……估计是因为下载了破解版,被系统屏蔽,重设一下安全设置就好: 很兴奋的打开系统偏好设置->安全性与隐私: 然而 ...

  10. node03

    1.express处理post请求 借助body-parse中间件,其实最终我们也不会使用这个 对于get请求,无需中间件,用req.query即可返回相应的数据 但是post我们尝试借助中间件处理 ...