LinkedList详解

LinkedList详解

LinkedList是List接口的一个主要的实现类之一,基于链表的实现。以java8为例来了解一下LinkedList的源码实现

继承关系

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

继承了AbstractSequentialList,实现了List接口、Deque接口、Cloneable接口、Serializable接口

关键变量

/**
* 链表的长度
*/

transient int size = 0;

/**
 * 头节点
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */

transient Node<E> first;

/**
 * 尾结点
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */

transient Node<E> last;

// 这里用到了一个内部类Node,看到Node的结构就知道是一个双向链表了,保存了前驱节点和后继节点
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;
  }
}

构造器

// 无参默认构造器
public LinkedList() {
}

/**
 * 传入一个集合作为参数
 */

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

public boolean addAll(Collection<? extends E> c) {
  return addAll(size, c);
}

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;
  // index == size时表示是从链表尾部添加元素,所以前置节点为当前链表的尾结点
  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);
    // 前置节点为null,表示这是头节点
    if (pred == null)
      first = newNode;
    else
      pred.next = newNode;
    pred = newNode;
  }
 // succ == null,说明是从尾部添加的,将最后一个节点赋给尾结点
  if (succ == null) {
    last = pred;
  } else {
    pred.next = succ;
    succ.prev = pred;
  }

  size += numNew;
  modCount++;
  return true;
}

// 获取index位置的节点
Node<E> node(int index) {
  // assert isElementIndex(index);
 // index小于链表长度的一半,从链表头部开始遍历
  if (index < (size >> 1)) {
    Node<E> x = first;
    for (int i = 0; i < index; i++)
      x = x.next;
    return x;
  } else { // index不小于链表长度的一半,从链表尾部开始遍历
    Node<E> x = last;
    for (int i = size - 1; i > index; i--)
      x = x.prev;
    return x;
  }
}

add方法

// 逻辑比较简单,与上边addAll类似,一通插
public void add(int index, E element) {
    checkPositionIndex(index);
  // index == size表示在链表尾部添加
    if (index == size)
        linkLast(element);
    else // 否则在中间插入  先找到index所在位置的节点
        linkBefore(element, node(index));
}

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

void linkBefore(E e, Node<E> succ) {
  // assert succ != null;
  final Node<E> pred = succ.prev;
  final Node<E> newNode = new Node<>(pred, e, succ);
  succ.prev = newNode;
  if (pred == null)
   first = newNode;
  else
   pred.next = newNode;
  size++;
  modCount++;
}

remove方法

LinkedList的remove方法比较

public E remove(int index) {
   // 检测该索引位置是否符合要求  index >= 0 && index < size
    checkElementIndex(index);
    return unlink(node(index));
}

// 找到当前索引位置的节点
Node<E> node(int index) {
  // assert isElementIndex(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;
  }
}

unlink(Node<E> x) {
  // assert x != null;
  // 所要删除的元素
  final E element = x.item;
  // 所要删除的元素的后继节点
  final Node<E> next = x.next;
  // 所要删除的元素前驱结点
  final Node<E> prev = x.prev;
 // 前驱节点为null,表示为头节点,头节点指向后继节点即可
  if (prev == null) {
    first = next;
  } else {
    // 前驱节点的后继节点指向该元素的后继节点
    prev.next = next;
    x.prev = null;
  }
 // 后继节点为null,表示为尾节点,尾节点指向前驱节点即可
  if (next == null) {
    last = prev;
  } else {
    // 后继节点的前驱节点指向该元素的前驱节点
    next.prev = prev;
    x.next = null;
  }

  x.item = null;
  size--;
  modCount++;
  return element;
}

迭代器

LinkedList使用的迭代器是其父类AbstractSequentialList中的iterator方法,其迭代器是双向的,可以正向也可以反向

// AbstractSequentialList类方法
public Iterator<E> iterator() {
    return listIterator();
}

// LinkedList类方法
public ListIterator<E> listIterator(int index) {
  checkPositionIndex(index);
  return new ListItr(index);
}
//  与ArrayList的迭代器不同的是,ArrayList的迭代器实现的是Iterator接口 而LinkedList为ListIterator接口,相当于ArrayList的listIterator()方法
//  Iterator只能向前遍历,ListIterator可以向前也可以向后
private class ListItr implements ListIterator<E{
    private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;

    ListItr(int index) {
        // assert isPositionIndex(index);
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }

    public boolean hasNext() {
        return nextIndex < size;
    }

    public E next() {
        checkForComodification();
        if (!hasNext())
            throw new NoSuchElementException();

        lastReturned = next;
        next = next.next;
        nextIndex++;
        return lastReturned.item;
    }

    public boolean hasPrevious() {
        return nextIndex > 0;
    }

    public E previous() {
        checkForComodification();
        if (!hasPrevious())
            throw new NoSuchElementException();

        lastReturned = next = (next == null) ? last : next.prev;
        nextIndex--;
        return lastReturned.item;
    }

    public int nextIndex() {
        return nextIndex;
    }

    public int previousIndex() {
        return nextIndex - 1;
    }

    public void remove() {
        checkForComodification();
        if (lastReturned == null)
            throw new IllegalStateException();

        Node<E> lastNext = lastReturned.next;
        unlink(lastReturned);
        if (next == lastReturned)
            next = lastNext;
        else
            nextIndex--;
        lastReturned = null;
        expectedModCount++;
    }

    public void set(E e) {
        if (lastReturned == null)
            throw new IllegalStateException();
        checkForComodification();
        lastReturned.item = e;
    }

    public void add(E e) {
        checkForComodification();
        lastReturned = null;
        if (next == null)
            linkLast(e);
        else
            linkBefore(e, next);
        nextIndex++;
        expectedModCount++;
    }

    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (modCount == expectedModCount && nextIndex < size) {
            action.accept(next.item);
            lastReturned = next;
            next = next.next;
            nextIndex++;
        }
        checkForComodification();
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

https://zhhll.icu/2021/java基础/集合/3.LinkedList详解/

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/209870.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

进制转化总结

来源&#xff0c;做个笔记&#xff0c;讲的还蛮清楚通信原理-2.5 数据封装与传输05_哔哩哔哩_bilibili ip地址范围

6 新建工程——寄存器

文章目录 6.1 本地新建工程文件夹6.2 新建工程6.2.1 选择CPU型号6.2.2 在线添加库文件6.2.3 添加文件6.2.4 复制存储器分配文件6.2.5 配置选项卡6.2.5.1 Linker6.2.5.2 Target6.2.5.3 Output 选项卡6.2.5.4 Listing 选项卡6.2.6 下载器配置 版本说明&#xff1a;MDK5.24 6.1 本…

许战海战略文库|重回大众视野的健力宝如何重生

摘要&#xff1a;销售额连续7年没有增长;产业主品牌定位不清晰;产品不协同缺少产品战略;子品牌无法形成合力新产品共性不足;过度差异化缺少渠道战略;被渠道能力更强的品牌挤压。火遍世界的“东方魔水”从第一品牌到被人遗忘&#xff0c;健力宝该如何重生? 健力宝诞生于1984年&…

css 三栏布局的实现

三栏布局在前端页面设计中是一个常见的布局方式&#xff0c;通常包含左侧、中间和右侧三个部分。这种布局方式在多种场景中都很受欢迎&#xff0c;例如博客、新闻网站和企业官网。本文将详细介绍三栏布局的实现方法&#xff0c;包括用法、代码、深入理解&#xff0c;以及配合高…

Python编程基础:输入/输出函数、注释与缩进

Python是一种简单易学的编程语言&#xff0c;广泛应用于Web开发、数据分析、人工智能等领域。无论您是初学者还是有一定编程经验的人士&#xff0c;都可以从Python的基础知识开始建立自己的编程技能。 目录 理论Python语言的发展程序设计语言的分类静态语言与脚本语言的区别 代…

解决element ui tree组件不产生横向滚动条

结果是这样的 需要在tree的外层&#xff0c;包一个父组件 <div class"tree"><el-tree :data"treeData" show-checkbox default-expand-all></el-tree></div> 在css里面这样写,样式穿透按自己使用的css编译器以及框架要求就好 &l…

笔记:Pika Labs 3D 动画生成工具

Pika Labs 一款3D 动画生成工具 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/134657306 目 录 1. 简介2. 准备2.1 安装 discord2.2 加入 Discord 频道 3. Pika 使用指南2.1 快速开始2.2 从图像到视频2.3 Pika Bot按钮2.4 提示&#xff08;Prompt&a…

WPF Live Charts2 自学笔记

文章目录 前言实现效果微软平台的历史问题 WPF 项目搭建Nuget添加额外框架添加项目初始化livecharts配置其它LiveCharts2 案例简单案例Demo示例ViewViewModel GPU渲染 Github地址仓库 前言 LiveChart 是C# 上面很受欢迎的统计图 UI控件。最近在学WPFhalcon开发&#xff0c;想想…

肖sir__mysql之单表练习题2__(2)

mysql之单表练习题 一.建表语句 create table grade(class int(4),chinese int(8),english int(4),math int(8),name varchar(20),age int(8),sid int(4)primary key auto_increment) DEFAULT charsetutf8; insert into grade(class,chinese,english,math,name,age)values(1833…

在Android上搭建一个NDK项目

首先New Project&#xff0c;选择Native C&#xff0c;点击Next。 填入项目名称和包名&#xff0c;点击Next。 这里我们选择Cmake默认的C版本。 创建好的项目目录&#xff0c;里面比我们正常的Android项目多了一个cpp目录 打开MainActivity。里面定义了一个jni方法stringFromJN…

LLM;超越记忆《第 2 部分 》

一、说明 在这篇博客中&#xff0c;我深入研究了将大型语言模型&#xff08;LLM&#xff09;提升到基本记忆之上的数学框架。我们探索了动态上下文学习、连续空间插值及其生成能力&#xff0c;揭示了 LLM 如何理解、适应和创新超越传统机器学习模型。 LLM代表了人工智能的重大飞…

如何使用 NFTScan NFT API 在 Starknet 网络上开发 Web3 应用

Starknet 是由以色列软件公司 StarkWare 开发的免许可的第 2 层网络。Starknet 作为以太坊上的 ZK Rollup 运行&#xff0c;帮助 dApp 使用 STARK 证明以更低的交易成本实现更大的计算规模。该网络允许智能合约与区块链上部署的其他合约进行交互&#xff0c;从而提高协议之间的…

简单说说vue中v-model和v-bind绑定数据的异同

vue的模板采用DOM模板&#xff0c;也就是说它的模板可以当做DOM节点运行&#xff0c;在浏览器下不报错&#xff0c;绑定数据有三种方式&#xff0c;一种是插值&#xff0c;也就是{{name}}的形式&#xff0c;一种是属性绑定 v-bind&#xff0c;还有一种是双向绑定 v-model。{{na…

Postman Post请求上传文件

Postman Post请求上传文件 一、选择post请求方式&#xff0c;输入请求地址 二、填写Headers Key&#xff1a;Content-Type Value&#xff1a;multipart/form-data [{"key":"Content-Type","value":"multipart/form-data","de…

校园局域网规划与设计(cisco仿真模拟)

摘 要 随着网络技术的发展&#xff0c;校园网的建设已经进入到一个蓬勃发展的阶段。校园网的建成和使用&#xff0c;对于提高教学和科研的质量、改善教学和科研条件、加快学校的信息化进程&#xff0c;开展多媒体教学与研究以及使教学多出人才、科研多出成果有着十分重要而深远…

深入理解前端路由:构建现代 Web 应用的基石(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Hdoop学习笔记(HDP)-Part.13 安装Ranger

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

MySQL的系统信息函数

系统信息函数让你更好的使用MySQL数据库 1、version()函数 查看MySQL系统版本信息号 select version();2、connection_id()函数 查看当前登入用户的连接次数 直接调用CONNECTION_ID()函数--不需任何参数--就可以看到当下连接MySQL服务器的连接次数&#xff0c;不同时间段该…

Jmeter性能测试 —— 压力模式

压力模式 性能测试中的压力模式有两种。 第一种是并发用户模式&#xff08;虚拟用户模式&#xff09;并发用户是指虚拟并发用户数&#xff0c;从业务角度&#xff0c;也可以理解为同时在线的用户数。 从客户端的角度出发&#xff0c;摸底业务系统各节点能同时承载的在线用户数…

shell 脚本批量处理文件后缀名

shell 脚本批量处理文件后缀名 flumes收集日常完成后会对收集的文件添加.COMPLETED后缀名。 我仍然使用原文件名&#xff0c;于是萌生了编写 shell 脚本批量删除文件后缀名的想法。 效果很好&#xff0c;但整个脚本功能太多单一&#xff0c;如果想按需修改文件后缀名呢&#xf…