【集合】LinkedList 详解

news2024/12/25 13:45:32

Java中的LinkedList是一种实现了List接口的双向链表数据结构。链表是由一系列节点(Node)组成的,每个节点包含了指向上一个节点的指针prev,数据item指向下一个节点next的指针
在这里插入图片描述

  • 实现了Deque接口,可以在两端进行操作(插入、删除)。并且由于LinkedList内部是基于链表实现的,所以插入、删除数据时只需要改变链表指针的指向,时间复杂度为O(1),而不需要进行数组的移动,所以它非常适合于频繁的插入、删除操作。但是LinkedList的缺点就是随机访问元素的速度较慢,因为需要从头开始遍历链表,时间复杂度为O(n)
  • 实现了Cloneable接口,支持克隆功能
  • 继承 Iterable 接口,可以使用 for-each 迭代
  • 实现了List接口,支持 增删改查 的功能

源码分析(JDK1.8)

成员变量属性

/**
 * 链表节点个数
 */
transient int size = 0;

/**
 * 永远指向第一个节点
 */
transient Node<E> first;

/**
 * 永远指向最末尾的节点
 */
transient Node<E> last;

/**
 * 静态内部类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;
    }
}

构造方法

LinkedList 有两个构造函数,空参构造方法,指定集合元素列表的构造方法

  • 空参构造
/**
 * 构造一个空列表
 */
 public LinkedList() {
 }
  • 指定集合元素列表的构造方法

/**
  *  按照集合迭代器返回的顺序,构造一个包含指定集合的元素的列表    
  */
  public LinkedList(Collection<? extends E> c) {
      this();
      addAll(c);
  }

/**
  * 将指定集合中的所有元素从此列表中的指定位置开始插入。将当前位于该位置的元素(如果有)      
  * 和任何后续元素向右移动(增加它们的索引)。新元素将按照指定集合的迭代器返回的顺序出现        
  * 在列表中。
  */
  public boolean addAll(int index, Collection<? extends E> c) {
      checkPositionIndex(index);  //校验index是否有效,是否超出链表的实际长度
  
       Object[] a = c.toArray();  //将集合转换成Object[] 数组
       int numNew = a.length;  
       if (numNew == 0) 
           return false;
  
       Node<E> pred, succ;
       // 如果索引值与链表的长度一致.那么将此指定集合中的所有元素放在链表的末尾
       if (index == size) {  
           succ = null;
           pred = last;
       } else {
       // 如果不一致,就需要将指定集合中的所有元素从此列表中的指定位置开始插入
           succ = node(index);   //从链表中获取当前index位置的节点Node
           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;
   }

add(), 插入节点方法

ArrayList 的add方法有两个,末尾添加指定位置添加

  • 末尾添加
    直接将新增节点添加在链表的最末尾处,然后指定新增节点的上一个节点prev为链表中的last节点,下一个节点next置为null
/**
  * 将指定的元素追加到此列表的末尾
  */
 public boolean add(E e) {
     linkLast(e);
     return true;
 }

 /**
   * 将e链接为最后一个元素.
   */
  void linkLast(E e) {
      final Node<E> l = last;  //先获取链表最后一个节点
      //将l节点设置为e元素节点上一个节点,因为是末尾添加,所以e元素的下一个节点next为null
      final Node<E> newNode = new Node<>(l, e, null);
      last = newNode;  //先标记新增加的e元素节点为链表最末尾节点
      if (l == null) 
          //如果l节点为null,说明当前链表没有任何节点,将新增的e元素节点设置为链表头节点
          first = newNode;
      else
          //当前链表有其他节点,将l节点的next下一个节点指定为新增的e元素节点
          l.next = newNode;
      size++;   //更新链表长度+1
      modCount++;  //记录链表数据结构的变化次数
  }
  • 指定位置添加
    也叫插队添加,会打乱原本链表中节点的存储顺序。
    在这里插入图片描述
public void add(int index, E element) {
     //判断索引下标是否在链表长度范围内,超出范围则报IndexOutOfBoundsException 
     checkPositionIndex(index);
     
     if (index == size)
         //指定位置index正好等于链表的长度,那么就是尾部添加节点
         linkLast(element);
     else
         //复杂一点,见下文源码
         linkBefore(element, node(index));
 }

/**
  * 指定位置index之前添加节点
  */
 void linkBefore(E e, Node<E> succ) {
     // assert succ != null;
     final Node<E> pred = succ.prev; //获取指定位置index节点的上一个节点pred
     //将新增e元素节点的上一个节点指定为succ.prev,下一个节点next指向为succ
     final Node<E> newNode = new Node<>(pred, e, succ); 
     succ.prev = newNode; //变更succ的上一个节点为新增e元素节点
     if (pred == null)
         //表明当前链表还没任何节点,标记新增e元素节点为链表的头节点
         first = newNode;
     else
         //表明当前链表有节点,
         pred.next = newNode,将succ.prev节点的下一个节点next更细为新增e元素节点
     size++;  //更新链表长度+1
     modCount++;  //记录链表数据结构的变化次数
 }

remove(), 删除元素方法

LinkedList 的remove方法有两个,指定节点删除指定位置删除

  • 指定节点删除
    由于LinkedList 允许元素重复,所以指定元素删除方法可能存在删除多个。
public boolean remove(Object o) {
     if (o == null) {
         //指定的节点元素item为null,那么就从链表的frist节点开始遍历,将所有节点之间解绑
         for (Node<E> x = first; x != null; x = x.next) {
             if (x.item == 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;
 }

/**
  * 解绑
  */
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) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;   //将前一个节点prve置为null
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;  //将下一个节点next置为null
    }

    x.item = null;  //将节点元素item置为null
    size--;   //链表节点个数 -1
    modCount++;
    return element;
 }
  • 指定位置删除
    最多只会删除一个元素,如果指定位置index超出数组结构长度,报错 IndexOutOfBoundsException
public E remove(int index) {
      checkElementIndex(index);  //校验index的有效性
      return unlink(node(index));  //先获取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) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;   //将前一个节点prve置为null
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;  //将下一个节点next置为null
    }

    x.item = null;  //将节点元素item置为null
    size--;   //链表节点个数 -1
    modCount++;
    return element;
 }

set(), 修改节点元素方法

LinkedList 中的修改方法只有一个,通过index下标来精准修改, 修改元素节点的步骤,其实就是同一个位置的节点item的覆盖操作
,set方法只修改节点的item信息,prev和next信息不变

 public E set(int index, E element) {
     checkElementIndex(index);  //校验index的有效性,IndexOutOfBoundsException
      Node<E> x = node(index); //先获取index位置的节点
      E oldVal = x.item;  
      x.item = element;  //重新赋值
      return oldVal;
    }

get(), 取元素方法

LinkedList 的get方法只有一个,只能通过索引下标index获取

public E get(int index) {
      checkElementIndex(index);  //校验index的有效性,IndexOutOfBoundsException
      return node(index).item;  //核心, 有算法
  }

Node<E> node(int index) {
    // assert isElementIndex(index);
   
    if (index < (size >> 1)) {   //用index与链表长度size的一半比较,右移>>位运算
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;  //从first开始,从前往后一个个遍历直到到达index所在位置
        return x;
    } else {
        Node<E> x = last;  //从last开始,从后向前一个个遍历直到到达index所在位置
        for (int i = size - 1; i > index; i--)
            x = x.prev;  
        return x;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

四大函数式接口(重点,必须掌握)

新时代程序员必须要会的 &#xff1a;lambda表达式、链式编程、函数式接口、Stream流式计算 什么是函数式接口 1.函数型接口 package com.kuang.function;import java.util.function.Function;/*** Function函数型接口 有一个输入参数&#xff0c;有一个输出* 只要是函数式接口…

华为云云耀云服务器L实例评测|利用云服务器部署个人博客站

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂 目录 前言效果图环境服务器购买安装宝塔面板环境搭建源码下载配置NGINX打包前端总结 前言 有句话说的好&#xff1a;在小的个体&#xff0c;也有自己的品牌。 就像我和腾讯一样&#xff0c…

爬虫笔记_

爬虫简介 爬虫初始深入 爬虫在使用场景中的分类 通用爬虫&#xff1a; 抓取系统重要组成部分。抓取的是一整张页面数据 聚焦爬虫&#xff1a; 是建立在通用爬虫的基础上。抓取的是页面中特定的局部内容。 增量式爬虫 监测网站中数据更新的情况。只会抓取网站中最新更新出来的…

unity学习第1天

本身也具有一些unity知识&#xff0c;包括Eidtor界面使用、Shader效果实现、性能分析&#xff0c;但对C#、游戏逻辑不太清楚&#xff0c;这次想从开发者角度理解游戏&#xff0c;提高C#编程&#xff0c;从简单的unity游戏理解游戏逻辑&#xff0c;更好的为工作服务。 unity201…

Linux内核编译机制

文章目录 KconfigKconfig语法 KbuildMakefile Linux内核的编译主要过程&#xff1a;配置、编译、安装。 配置主要由Kconfig提供图形界面完成编译主要基于Kbuild编译系统&#xff0c;执行make完成编译安装主要也是基于Kbuild提供的脚本&#xff0c;然后执行make完成安装 Kconf…

【需求侧响应】综合能源中多种需求响应——弹性电价、可平移及可削减研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Java 函数式编程思考 —— 授人以渔

引言 最近在使用函数式编程时&#xff0c;突然有了一点心得体会&#xff0c;简单说&#xff0c;用好了函数式编程&#xff0c;可以极大的实现方法调用的解耦&#xff0c;业务逻辑高度内聚&#xff0c;同时减少不必要的分支语句&#xff08;if-else&#xff09;。 一、函数式编…

Openresty(二十一)ngx.balance和balance_by_lua灰度发布

一 openresty实现灰度发布 ① 灰度发布 说明&#xff1a; 早期博客对灰度发布的概念进行解读,并且对原生 nginx灰度实现进行讲解后续&#xff1a; 主要拿节点引流的灰度发布,并且关注gray灰度策略 相关借鉴 ② 回顾HTTP反向代理流程 ngx_http_upstream 可操作点&#…

数据结构与算法之树、森林与二叉树的转换(手绘)

树、森林与二叉树的转换 树、森林与二叉树的转换树转换成二叉树原则&#xff1a;步骤展示连线给出除长子外的结点去线层次调整 森林转换成二叉树原则步骤展示根据树转换成二叉树的原则将每颗树转变成二叉树第 n 棵树作为第 n-1 棵树根节点的右子节点 二叉树转换成树原则步骤展示…

KDM CCA Secure FHE

参考文献&#xff1a; [BFM88] Blum M, Feldman P, Micali S. Non-interactive zero-knowledge and its applications[M]//Providing Sound Foundations for Cryptography: On the Work of Shafi Goldwasser and Silvio Micali. 2019: 329-349.[FS90] Feige U, Shamir A. Witn…

智能合约漏洞案例,Euler Finance 1.96 亿美元闪电贷漏洞分析

智能合约漏洞案例&#xff0c;Euler Finance 1.96 亿美元闪电贷漏洞分析 2023 年 3 月 13 日上午 08:56:35 UTC&#xff0c;DeFi 借贷协议 Euler Finance 遭遇闪电贷攻击。 Euler Finance 是一种作为无许可借贷协议运行的协议。其主要目标是为用户提供各种加密货币的借贷便利。…

免单商城系统小程序开发源码功能解析

商品免单是现在很多商家喜欢做的一种营销活动&#xff0c;市面上几乎所有商家都是利用免单系统进行免单活动的&#xff0c;但大部分的免单系统仅仅只有排队免单功能&#xff0c;免单的周期长类目单一。我们的免单系统是将获客、拉新、留存、转化集于一身&#xff0c;多种免单拓…

什么是Java中的“内存屏障“(Memory Barrier)?它们有什么作用?

内存屏障是一种用于控制内存访问顺序的指令。在多核处理器上运行的多线程程序可能会因处理器的乱序执行和缓存一致性问题而导致意外的行为。内存屏障可以用来强制某些操作的顺序&#xff0c;以确保线程间的正确协同。 作用包括&#xff1a; 保证写入的可见性&#xff1a;内存…

前缀和实例4(和可被k整除的子数组)

题目&#xff1a; 给定一个整数数组 nums 和一个整数 k &#xff0c;返回其中元素之和可被 k 整除的&#xff08;连续、非空&#xff09; 子数组 的数目。 子数组 是数组的 连续 部分。 示例 1&#xff1a; 输入&#xff1a;nums [4,5,0,-2,-3,1], k 5 输出&#xff1a;7 …

Linux驱动IO篇——异步通知

文章目录 什么是异步通知异步通知和异步IO的区别信号含义应用层使用信号驱动如何实现异步信号驱动实例 什么是异步通知 异步通知在Linux的实现中是通过信号&#xff0c;而信号是在软件层次上对中断机制的一种模拟。这种机制和中断非常类似&#xff0c;所以可以以中断的思想来理…

AI绘画:如何让图片开口说话生成视频?变现渠道有哪些?

如何让AI绘画做出来的视频可以开口说话&#xff0c;本篇文章给你讲解清楚。 这个项目市面上有很多种叫法&#xff0c;AI数字人&#xff0c;图片说话&#xff0c;图片数字人等等。 废话不多说&#xff0c;直接以AI小和尚为例进行实操。 1.生成图片&#xff1a; 用Midjourney…

操作系统期末复习笔记

文章目录 操作系统第1章 计算机系统概述1 指令执行的基本指令周期2 中断分类与中断处理过程2.1 中断的定义2.2 中断分类2.3 中断的意义2.4 无中断2.5 有中断2.6 中断和指令周期2.7 中断处理的过程 3 处理多中断的两种方法3.1 顺序中断处理&#xff08;禁止中断&#xff09;3.2 …

大数据-玩转数据-Flink恶意登录监控

一、恶意登录 对于网站而言&#xff0c;用户登录并不是频繁的业务操作。如果一个用户短时间内频繁登录失败&#xff0c;就有可能是出现了程序的恶意攻击&#xff0c;比如密码暴力破解。 因此我们考虑&#xff0c;应该对用户的登录失败动作进行统计&#xff0c;具体来说&#x…

批量获取CSDN文章对文章质量分进行检测,有助于优化文章质量

&#x1f4da;目录 ⚙️简介✨分析获取步骤⛳获取文章列表☘️前期准备✨ 接口解析⚡️ 获取文章的接口 ☄️文章质量分接口⭐接口分析 ⌛代码实现&#xff1a;⚓核心代码:⛵测试用例:⛴ 运行效果:☘️增加Excel导出 ✍️结束 ⚙️简介 有时候我们写文章是为了记录当下遇到的bu…

乙方策划人员的内心独白:写不完的案子,是工作的常态吗?

在某种程度上来说&#xff0c;这是对的。 如果是年轻人来说&#xff0c;在甲方当策划就是当执行&#xff0c;只有积累一定经验才能真正实行策划任务、 而在乙方做策划那就是纯纯的策划&#xff0c;也就是你说的每天写不完的案子。 对于普通人的职场选择往往是就近选择&#…