1、简介

LinkedList 底层使用的是 双向链表的数据结构

2、类图(JDK 1.8)

下图是LinkedList实现的接口和继承的类关系图:

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

1、实现了四个接口

1.1 java.util.List 接口,提供数组的添加、删除、修改、迭代遍历等操作。

1.2 java.io.Serializable 接口,表示 LinkedList 支持序列化的功能。

1.3 java.lang.Cloneable 接口,表示 LinkedList 支持克隆。

1.4 java.util.Deque接口,是一种双向线性集合,可以再两端对集合进行插入或者删除操作,所以既可以当队列(FIFO),也可以当栈(LIFO),Deque继承java.util.Queue。

2、继承了一个类

2.1 java.util.AbstractSequentialList抽象类,它继承了AbstractList抽象类,并且有

public abstract ListIterator<E> listIterator(int index);抽象方法,返回一个由LinkedList实现的ListIterator迭代器,用来支持AbstractList抽象类的get(int index),set(int index, E element),add(int index, E element)remove(int index) 等一些支持随机访问的方法。

3、常用方法

添加元素 add(E e)

    //链表添加元素
public boolean add(E e) {
linkLast(e);//往链表尾部添加元素
return true;
}
void linkLast(E e) {
final Node<E> l = last;//临时变量l=last,后面会直接修改last节点 指向 新加入的节点
final Node<E> newNode = new Node<>(l, e, null);//新元素,创建成一个节点
last = newNode;//最后节点,指向新加入的节点
if (l == null)//判断last是否为空,为空代表是第一次新增节点
first = newNode;//首节点指向新加入的节点
else//last不为空表示当前链表有值,让l节点的参数 next指向新加入的节点
l.next = newNode;
size++;//链表大小+1
modCount++;//链表修改次数+1
} 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;
}
}

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

    public void add(int index, E element) {
checkPositionIndex(index);//判断index是否>=0,并且index<=size,否则抛出IndexOutOfBoundsException异常 if (index == size)//判断index等于size直接添加到链表尾部
linkLast(element);//同add(E e)方法
else
linkBefore(element, node(index));//node()方法先根据index标在链表取得对应到Node节点,并把新元素,添加到Node节点之前
} //根据下标获得节点
Node<E> node(int index) {
// assert isElementIndex(index); //size >> 1 等同 size除以2
//判断index 下标是在链表的前半部分,是则从链表头部开始遍历,否则从链表尾部向前遍历
//加了一个判断,最多只需要遍历一半链表即可,复杂度从O(n)降到了O(1)
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;
}
}

根据下标删除节点 remove(int index)

   //根据下标删除节点
public E remove(int index) {
checkElementIndex(index);//判断下标是否越界
return unlink(node(index));
} //链表-删除节点
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;//删除节点的上一个节点 if (prev == null) {//prev等于null,说明是第一个节点
first = next;
} else {
prev.next = next;//删除节点的上一个节点,next参数指向 删除节点的下一个节点
x.prev = null;//删除节点的上一个节点,设置为null取消关联
} if (next == null) {//next等于null,说明是尾部节点
last = prev;
} else {
next.prev = prev;//删除节点的下一个节点,参数prev指向 删除节点的上一个节点
x.next = null;//删除节点的下一个节点,设置为null取消关联
} x.item = null;//节点元素设置为null,方便gc回收
size--;//链表大小-1
modCount++;//链表修改次数+1
return element;//返回删除的元素值
}

根据元素删除节点 remove(Object o)

    //删除链表中第一个匹配o的元素
public boolean remove(Object o) {
if (o == null) {//先判断o是否为空
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {//判断元素是否为null
unlink(x);//链表删除元素
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {//判断元素是否相等
unlink(x);//链表删除元素
return true;
}
}
}
return false;
}

根据下标获得元素 get(int index)

    //根据下标获得节点
public E get(int index) {
checkElementIndex(index);//判断index是大于0,并且小于等于size,否则抛出异常
return node(index).item;//node()方法获取下标对应的Node,并返回Node的item
}

时间复杂度

添加元素 add(E e)

最好时间复杂度是 O(1) ,最坏时间复杂度是 O(n) ,平均时间复杂度是 O(n) 。

最好时间复杂度发生在头部、或尾部添加的情况。

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

最好时间复杂度是 O(1) ,最坏时间复杂度是 O(n) ,平均时间复杂度是 O(n) 。

最好时间复杂度发生在头部、或尾部添加的情况。

根据下标删除节点 remove(int index)

最好时间复杂度是 O(1) ,最坏时间复杂度是 O(n) ,平均时间复杂度是 O(n) 。

最好时间复杂度发生在头部、或尾部移除的情况。

根据元素删除节点 remove(Object o)

最好时间复杂度是 O(1) ,最坏时间复杂度是 O(n) ,平均时间复杂度是 O(n) 。

最好时间复杂度发生在头部移除的情况。

根据下标获得元素 get(int index)

最好时间复杂度是 O(1),平均时间复杂度是 O(n) ,查找指定元素的平均时间复杂度是 O(n) 。

最好时间复杂度发生在下标为0或者等于size的位置,因为LinkedList会根据index是否超过了 size除以2 来作为依据看是从链表头部往后面遍历,还是链表尾部向前遍历,所以基本上只需要遍历一半的链表就能找到对应下标的元素了。

小结

1、LinkedList做插入、删除的时候,慢在寻址,快在只需要改变前后Node的引用地址即可

2、适用于频繁的元素修改的场景,对于查询场景比较多的适合请适用ArrayList这种数据结构

java集合-链表LinkedList的更多相关文章

  1. 【Java集合】LinkedList详解前篇

    [Java集合]LinkedList详解前篇 一.背景 最近在看一本<Redis深度历险>的书籍,书中第二节讲了Redis的5种数据结构,其中看到redis的list结构时,作者提到red ...

  2. Java集合:LinkedList源码解析

    Java集合---LinkedList源码解析   一.源码解析1. LinkedList类定义2.LinkedList数据结构原理3.私有属性4.构造方法5.元素添加add()及原理6.删除数据re ...

  3. 死磕 java集合之LinkedList源码分析

    问题 (1)LinkedList只是一个List吗? (2)LinkedList还有其它什么特性吗? (3)LinkedList为啥经常拿出来跟ArrayList比较? (4)我为什么把LinkedL ...

  4. java集合之linkedList链表基础

    LinkedList链表: List接口的链接列表实现.允许存储所有元素(包含null).使用频繁增删元素. linkedList方法: void addFirst(E e) 指定元素插入列表的开头 ...

  5. Java 集合之LinkedList源码分析

    1.介绍 链表是数据结构中一种很重要的数据结构,一个链表含有一个或者多个节点,每个节点处理保存自己的信息之外还需要保存上一个节点以及下一个节点的指针信息.通过链表的表头就可以访问整个链表的信息.Jav ...

  6. Java集合干货——LinkedList源码分析

    前言 在上篇文章中我们对ArrayList对了详细的分析,今天我们来说一说LinkedList.他们之间有什么区别呢?最大的区别就是底层数据结构的实现不一样,ArrayList是数组实现的(具体看上一 ...

  7. Java集合(五)--LinkedList源码解读

    首先看一下LinkedList基本源码,基于jdk1.8 public class LinkedList<E> extends AbstractSequentialList<E> ...

  8. Java集合:LinkedList (JDK1.8 源码解读)

    LinkedList介绍 还是和ArrayList同样的套路,顾名思义,linked,那必然是基于链表实现的,链表是一种线性的储存结构,将储存的数据存放在一个存储单元里面,并且这个存储单元里面还维护了 ...

  9. Java集合之LinkedList

    一.LinkedList概述 1.初识LinkedList 上一篇中讲解了ArrayList,本篇文章讲解一下LinkedList的实现. LinkedList是基于链表实现的,所以先讲解一下什么是链 ...

随机推荐

  1. 2021华为软件精英挑战赛(C/C++实现)-苦行僧的实现过程

    下面给出2021华为软件精英挑战赛参与的整个过程,虽然成绩不是很好,但是也是花了一些时间的,希望后面多多学习,多多进步. 代码已经上传到了Github上:https://github.com/myFr ...

  2. Ubuntu-搭建Clang Static Analyzer环境

    其实也就是一个开源的漏洞扫描器 专门扫描C/C++ 0BJECT-C++这种,实不相瞒我搭建了5天这个环境,最后我发现了一种超级方便的办法 前面怎么走的坑还是不分享了吧,由于没有看到前面很多人的办法或 ...

  3. Mybatis自定义拦截器与插件开发

    在Spring中我们经常会使用到拦截器,在登录验证.日志记录.性能监控等场景中,通过使用拦截器允许我们在不改动业务代码的情况下,执行拦截器的方法来增强现有的逻辑.在mybatis中,同样也有这样的业务 ...

  4. MyBatis-Plus Generator自定义模板

    相信大家在开发过程中,应该都用过Mybatis-Plus的Generator,但是可能没有自定义过模板并使用. 每个项目都应该有一个从Controller层到Mapper层的通用模板,来去掉哪些简单的 ...

  5. SQL语句练习(进阶版)

    学生数据库中有三个基本表(关系)如下: 学生表S(Sno,Sname,Age,Sex,SD) 课程表C(Cno,Cname, Teacher) 选课表SC(Sno,Cno,Grade) 请用SQL语言 ...

  6. 如何调试 Java 开源项目源码,记一种源码导入开发工具并调试的通用方法

    楔子 说起读开源项目源码,很多朋友觉得高大上.大佬牛逼,云云~ 挡在很多人面前的不是源码怎么读,而是不知道如何导入源码到开发工具以及如何调试源码. 本文将以 spring-cloud-gateway ...

  7. WebStorm 2020.1.2 激活

    1 下载 没下载的先去官网下载. 2 安装 系统Linux,解压后直接运行bin下的webstorm.sh. 首先提示是否导入设置,如果以前安装过的话会默认选择第一项. 选UI主题: 是否创建Desk ...

  8. Day12_62_线程的生命周期

    线程的生命周期 要实现多线程,必须在主线程中创建新的线程对象. 任何线程一般都具有五种状态,即创建,就绪,运行,阻塞,终止(消亡) 新建状态:在程序中创建了一个新的线程对象后,新的线程对象便处于新建状 ...

  9. kubespray续签k8s证书

    查看证书过期时期 [root@node1 ~]# openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text |grep ' Not ...

  10. 无所不能的PowerMock,mock私有方法,静态方法,测试私有方法,final类

    1.为什么要用mock 我的一本书的解释: (1)创建所需的DB数据可能需要很长时间,如:调用别的接口,模拟很多数据 (2)调用第三方API接口,测试很慢, (3)编写满足所有外部依赖的测试可能很复杂 ...