LInkedList总结及部分底层源码分析

1. LinkedList的实现与继承关系

  1. 继承:AbstractSequentialList 抽象类
  2. 实现:List 接口
  3. 实现:Deque 接口
  4. 实现:Cloneable 接口
  5. 实现:Serializable 接口,标记该类支持序列化

2. LinkedList的底层数据结构

  • LinkedList底层是基于双向链表实现的

1. 特点

链表在内存中存储空间是不连续的,只是利用相邻个节点之间的地址指向来保证存储在一条链表上的数据连续。

双向链表意思是一个节点有两个指向,一个指向后一个节点的地址(next),另一个指向前一个节点的地址(prev)。

主要实现的链表,在遍历时可以从头节点遍历到位节点,也可以从尾节点遍历到头节点。每个节点可以存储null

1. 单向链表举例:

​ 张三认识李四知道李四的住址,李四认识王五知道王五的住址,王五认识赵六知道赵六的地址。那么用单链表来表示可以表示为如下:

  • 说明:一个节点(node)有两个域,数据域(data)用于存放数据和指针域(next)用于指向下一个节点,所以这个指针域实际上保存的是下一个节点的地址。

2. 双向链表举例:

张三认识李四知道李四的住址,李四也知道张三的住址,李四认识王五知道王五的住址,王五也知道李四的住址。

双向链表有上域,两个指针域next:指向下一个节点,prev:指向前一个节点,数据域data:存放数据

双向链表表示:

2. 优点

  1. 以充分利用内存中散列的零碎的空间进行数据的存储。提高内存空间的利用率。

  2. 在进行数据插入和删除数据时执行效率快,因为在添加和删除数据时只需要改变指针的指向即可

    添加节点画图说明:

    删除节点画图说明:

3. 缺点

  1. 在链表存储的数据量比较大时,查找指定数据的效率较低,因为链表在查找指定的数据时,只能通过遍历的方式逐个去进行比较。不能像数组那样根据下标去进行查找。所以效率对较低。

  2. LinkedList集合是线程不安全的集合,在多线程情况下可能会发生线程安全问题。如果需要在多线程情况下使用该集合。需要使用Collections集合工具类创建一个线程安全的LinkedList集合

    // 创建一个线程安全的LinkedList集合
    List<Integer> linkedList = Collections.synchronizedList(new LinkedList<Integer>());

3. LinkedList的适用场景

  1. 单线程环境下对集合中存储的数据频繁进行添加新数据或者删除旧数据适合使用LinkedList集合
  2. 如果需要在多线程情况下使用需要创建线程安全的LinkedList集合

4. 底层源码分析

1. 构造函数

2.无参构造函数

/**
* 默认无参构造函数,
*/
public LinkedList() {}

2. 有参构造函数

/**
* 在初始化的时候直接将一个集合传入,可以把传入集合的元素全部复制到创建的新集合中
* @param c 传入的集合
*/
public LinkedList(Collection<? extends E> c) {
// 调用无参构造
this();
// addAll()地方,添加传入集合中所有的元素
addAll(c);
}
  • addAll() 方法

    /**
    * 将一个集合传入,可以把传入集合的元素全部复制到创建的新集合中
    * @param c 传入的集合
    * @return 成功返回true,失败返回false
    */
    public boolean addAll(Collection<? extends E> c) {
    // 调用addAll() 重载的方法
    return addAll(size, c);
    }
  • addAll重载的方法(目前不去探究该方法的底层实现)

    /**
    *
    * @param index
    * @param c
    * @return
    */
    public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index); Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew == 0)
    return false; Node<E> pred, succ;
    if (index == size) {
    succ = null;
    pred = last;
    } else {
    succ = node(index);
    pred = succ.prev;
    } for (Object o : a) {
    @SuppressWarnings("unchecked") E e = (E) o;
    Node<E> newNode = new Node<>(pred, e, null);
    if (pred == null)
    first = newNode;
    else
    pred.next = newNode;
    pred = newNode;
    } if (succ == null) {
    last = pred;
    } else {
    pred.next = succ;
    succ.prev = pred;
    } size += numNew;
    modCount++;
    return true;
    }

2. 添加一条数据的完整流程

1. 将数据包装

  1. 在每次添加数据时,如果数据是基本数据类型,会先将基本数据类型进行装箱操作,把基本数据类型转换成对应的包装类型(引用数据类型)
// 例如:集合中存放Integer数据类型,在进行add操作时,会先进行装箱操作

/**
* 将基本数据类转换为引用数据类型
* @param i 传入的参数为一个基本型数据类型
* @return 返回的参数是一个基本数据类型的包装类(引用数据类型)
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

2. 调用add方法进行add操作

/**
* 从尾部添加新节点的方法
* @param e
* @return
*/
public boolean add(E e) {
// 调用从尾部添加新节点的方法
linkLast(e);
return true;
}

3. linkLast() 从尾部添加新节点

/**
* 从链表尾部添加新节点
* @param e 要添加的新节点
*/
void linkLast(E e) {
// 将 last 尾部节点标记赋给辅助节点 l,last每次添加结束后会指向链表最后的一个节点
final Node<E> l = last; // 调用内部类Node 将新创建的节点挂到 l 节点后面,e.next = null,e.prev = l,e.item = e
final Node<E> newNode = new Node<>(l, e, null); // 将刚刚添加的新节点赋给尾部节点 last 标记该节点是尾节点,
// 此时last又指向了最后一个节点,L指向最后节点的前面一个节点
last = newNode; // 判断 l 指向是否为空,如果 l 为空说明一开始 last 为空,则证明添加的节点是该链表的第一个节点。
if (l == null)
first = newNode; // 将头节点标记指向该节点 // 反之将尾节点 last 的前一个节点 l 指向新节点,完成了新节点在链表尾部的添加
else
l.next = newNode; // 将链表实际长度加 1
size++;
// 将链表被修改的次数加 1
modCount++;
}
  • 变量说明:

    // 用于记录在链表中实际存储的元素个数,不参与序列化
    transient int size = 0; // 用于记录链表被修改的次数,不参与序列化
    protected transient int modCount = 0;
  • 内部类

    /**
    * LinkedList集合的一个静态内部类,用于封装新创建的节点
    */
    private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev; /**
    * 用于给新创建的节点
    * @param prev 用于指向旧链表的尾节点
    * @param element 存储新节点的数据
    * @param next 指向下一个节点
    */
    Node(Node<E> prev, E element, Node<E> next) {
    this.item = element;
    this.next = next;
    this.prev = prev;
    }
    }

5. LinkedList三种添加节点的方法

1. 头插法

在链表的头部添加新的节点

对应方法:

linkFirst(存储的数据值)

2. 尾插法

在链表的尾部添加新的节点

对应方法:

linkLast(存储的数据值)

3. 在链表中间插入节点

对应方法:

add(指定位置的索引值, 存储的数据值)

LInkedList总结及部分底层源码分析的更多相关文章

  1. List-LinkedList、set集合基础增强底层源码分析

    List-LinkedList 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 继上一章继续讲解,上章内容: List-ArreyLlist集合基础增强底层源码分析:https:// ...

  2. List-ArrayList集合基础增强底层源码分析

    List集合基础增强底层源码分析 作者:Stanley 罗昊 [转载请注明出处和署名,谢谢!] 集合分为三个系列,分别为:List.set.map List系列 特点:元素有序可重复 有序指的是元素的 ...

  3. Vector总结及部分底层源码分析

    Vector总结及部分底层源码分析 1. Vector继承的抽象类和实现的接口 Vector类实现的接口 List接口:里面定义了List集合的基本接口,Vector进行了实现 RandomAcces ...

  4. 集合之LinkedList(含JDK1.8源码分析)

    一.前言 LinkedList是基于链表实现的,所以先讲解一下什么是链表.链表原先是C/C++的概念,是一种线性的存储结构,意思是将要存储的数据存在一个存储单元里面,这个存储单元里面除了存放有待存储的 ...

  5. JDK1.8中LinkedList的实现原理及源码分析

    详见:https://blog.csdn.net/cb_lcl/article/details/81222394 一.概述           LinkedList底层是基于双向链表(双向链表的特点, ...

  6. JAVA ArrayList集合底层源码分析

    目录 ArrayList集合 一.ArrayList的注意事项 二. ArrayList 的底层操作机制源码分析(重点,难点.) 1.JDK8.0 2.JDK11.0 ArrayList集合 一.Ar ...

  7. 分布式缓存技术之Redis_Redis集群连接及底层源码分析

    目录 1. Jedis 单点连接 2. Jedis 基于sentinel连接 基本使用 源码分析 本次源码分析基于: jedis-3.0.1 1. Jedis 单点连接   当是单点服务时,Java ...

  8. Servlet和Tomcat底层源码分析

    Servlet 源码分析   Servlet 结构图 Servlet 和 ServletConfig 都是顶层接口,而 GenericServlet 实现了这两个顶层接口,然后HttpServlet ...

  9. Java的LinkedList底层源码分析

    首先我们先说一下,源码里可以看出此类不仅仅用双向链表实现了队列数据结构的功能,还提供了链表数据结构的功能.

随机推荐

  1. 单片机I/O口推挽与开漏输出详解(力荐)

    推挽输出:可以输出高,低电平,连接数字器件;推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止. 开漏输出:输出端相当于三极管的集电极. 要得到高电平状态需要上拉电 ...

  2. Linux入门需要搞清楚的思路问题

    很多同学接触linux不多,对linux平台的开发更是一无所知. 而现在的趋势越来越表明,作为一个优秀的软件开发人员,或计算机it行业从业人员,="" 掌握linux是一种很重要的 ...

  3. 检查是否是BST 牛客网 程序员面试金典 C++ java Python

    检查是否是BST 牛客网 程序员面试金典  C++ java Python 题目描述 请实现一个函数,检查一棵二叉树是否为二叉查找树. 给定树的根结点指针TreeNode* root,请返回一个boo ...

  4. docker创建本地主机实例Virtualbox 驱动出错

    宿主机系统:Centos7 64位 创建主机实例Virtualbox 命令:docker-machine create -d virtualbox test 连接centos工具:Finalshell ...

  5. Charles抓包 mock数据和rewrite功能

    一.mock数据 mock:在后端返回异常或需要=改前端展示的数据时可以模拟返回的response 1.1 抓到接口后 右击保存response到本地,后缀改成.json打开可以看到是把json保存下 ...

  6. 记一次性能优化的心酸历程【Flask+Gunicorn+pytorch+多进程+线程池,一顿操作猛如虎】

    您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦. 本文只是记录我优化的心酸历程.无他,唯记录尔.....小伙伴们可围观,可打call,可以私信与我交流. 干货满满,建议收藏,需要用到时常看看. 小 ...

  7. OPPO 图数据库平台建设及业务落地

    本文首发于 OPPO 数智技术公众号,WeChat ID: OPPO_tech 1.什么是图数据库 图数据库(Graph database)是以图这种数据结构存储和查询的数据库.与其他数据库不同,关系 ...

  8. 跟着老猫来搞GO-容器(1)

    前期回顾 前面的一章主要和大家分享了GO语言的函数的定义,以及GO语言中的指针的简单用法,那么本章,老猫就和大家一起来学习一下GO语言中的容器. 数组 数组的定义 说到容器,大家有编程经验的肯定第一个 ...

  9. Spring Cloud Gateway实战之五:内置filter

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  10. [atARC126F]Affine Sort

    记$g(k)$为$c$恰为$k$的合法三元组数,显然$f(k)=\sum_{i=1}^{k}g(i)$ 结论:若$\lim_{k\rightarrow \infty}\frac{g(k)}{k^{2} ...