当前位置: 首页 > news >正文

Java基础——集合进阶4

一、LinkedList集合

1.1 什么是 LinkedList?

LinkedList 是 Java 集合框架中 List 接口的一个基于双向链表(Doubly Linked List)实现的可变长列表

✅ 核心定义:

  • 实现了 ListDeque(双端队列)、CloneableSerializable 接口
  • 底层使用双向链表存储元素
  • 线程不安全
  • 允许存储 null 值,且允许多个 null

💡 因为实现了 Deque,所以 LinkedList 也可以当作 栈(Stack)或队列(Queue) 使用!

1.2 LinkedList 的作用与适用场景

主要作用:

  • 提供一个插入/删除高效、无需连续内存的有序容器
  • 支持在任意位置高效增删(前提是已定位到节点)
  • 可作为 双端队列(Deque):支持首尾插入/删除

✅ 适用场景(相对小众但关键):

  • 需要频繁在列表开头或中间插入/删除元素
  • 需要实现 队列(FIFO)或栈(LIFO) 功能(如任务调度、撤销操作)
  • 元素数量动态变化大,且无法预估大小
  • 对内存连续性无要求,且读取操作较少

⚠️ 注意:日常开发中,90% 场景仍推荐 ArrayList,LinkedList 仅用于特定性能需求!

1.3 常用方法与代码示例

1. 作为 List 使用(继承自 List 接口)

方法说明时间复杂度
add(E e)在末尾添加O(1)
add(int index, E e)在指定位置插入O(n)(需遍历定位)
get(int index)获取指定索引元素O(n)
remove(int index)删除指定索引元素O(n)(需遍历)
set(int index, E e)替换元素O(n)

❗ 虽然链表“插入快”,但 通过索引操作仍需 O(n) 时间(因为要从头/尾遍历找位置)!

2. 作为 Deque(双端队列)使用(核心优势!)

方法说明时间复杂度
addFirst(E e) / offerFirst(E e)头部插入O(1)
addLast(E e) / offerLast(E e)尾部插入O(1)
removeFirst() / pollFirst()头部删除O(1)
removeLast() / pollLast()尾部删除O(1)
peekFirst() / peekLast()查看首/尾元素O(1)

3.LinkedList独有的方法

但是这些方法在以后用的很少,只需要了解即可。

import java.util.*;public class LinkedListDemo {public static void main(String[] args) {// 1. 作为 List 使用List<String> list = new LinkedList<>();list.add("A");list.add(1, "B"); // 插入到索引1(需遍历,O(n))System.out.println(list.get(0)); // "A"(需遍历,O(n))// 2. 作为 Queue(队列)使用Queue<String> queue = new LinkedList<>();queue.offer("Task1");queue.offer("Task2");System.out.println(queue.poll()); // "Task1"(FIFO)// 3. 作为 Stack(栈)使用(推荐用 Deque)Deque<String> stack = new LinkedList<>();stack.push("Page1");stack.push("Page2");System.out.println(stack.pop()); // "Page2"(LIFO)// 4. 双端操作(Deque 特有)Deque<Integer> deque = new LinkedList<>();deque.addFirst(1);deque.addLast(2);System.out.println(deque.getFirst()); // 1System.out.println(deque.getLast());  // 2}
}

1.4 底层原理详解

1. 底层数据结构:双向链表(Node)

每个元素封装在一个 Node 节点中:

2. 关键成员变量

size:节点的个数;

Node<E> fisrt :头结点;

Node<E> last: 尾结点。

  • first 和 last 分别指向链表的首尾,使得首尾操作均为 O(1)
  • 整个链表不要求内存连续,节点分散在堆中

3.关键源码解析(以 JDK 8 为例)

add()方法:调用linkLast();

作用:将元素 e 添加到链表末尾(即 last 后面),并返回 true

linkLast:不断的创建节点并且把节点添加到链表的过程中。

void linkLast(E e) {final Node<E> l = last;          // 记录当前最后一个节点final Node<E> newNode = new Node<>(l, e, null); // 创建新节点last = newNode;                  // 更新 last 指向新节点if (l == null)                   // 如果原链表为空first = newNode;             // 首节点也指向它elsel.next = newNode;            // 原最后一个节点的 next 指向新节点size++;modCount++;
}

✅ 第一步:new LinkedList<>(); —— 初始化

first = null ; last = null ; size = 0 ;

内存中没有任何节点,📌 状态:空链表
 

✅ 第二步:list.add("aaa");

①执行 linkLast("aaa")

代码1:final Node<E> l = last; l = null(因为链表为空)

代码2:final Node<E> newNode = new Node<>(l, "aaa", null); → 创建新节点:

prev = null(因为是第一个节点);item = "aaa";next = null ;

假设这个节点在内存中的地址是 0x0011。

代码3:last = newNode;last 现在指向 0x0011

代码4:if (l == null) → 成立
first = newNode;
first 也指向 0x0011

代码5:size++size = 1

代码6:modCount++ → 修改次数增加

结果:头尾指针指向null,表示这是第一个节点即头结点,值为"aaa"。 

first → [null] → ["aaa"] → [null]↑last

✅ 第三步:list.add("bbb");

执行 linkLast("bbb")

代码1:final Node<E> l = last;l = 0x0011(当前最后一个节点)

代码2:final Node<E> newNode = new Node<>(l, "bbb", null);→ 创建新节点:

prev = 0x0011(前一个节点); item = "bbb" ; next = null ;

假设地址为 0x0022。

代码3:last = newNode;last 指向 0x0022;

代码4:if (l == null) → 不成立
l.next = newNode;
→ 将 0x0011next 指针指向 0x0022

代码5:size++size = 2 节点长度+1

结果:节点2的prev指针指向0x0011,值为"bbb",next指向null 表示尾指针

0x0011的next指向0x0022。

---------------

first → [null] → ["aaa"] → ["bbb"] → [null]↑           ↑0x0011       0x0022↑last

🧱 四、关键点总结(结合图示)

步骤操作内存变化
1. 初始化new LinkedList()first=nulllast=null
2. 添加第一个元素add("aaa")创建节点 0x0011first 和 last 都指向它
3. 添加第二个元素add("bbb")创建 0x0022prev=0x00110x0011.next=0x0022last=0x0022
4. 添加第三个元素add("ccc")创建 0x0033prev=0x00220x0022.next=0x0033last=0x0033

1.5、LinkedList vs ArrayList 详细对比

特性LinkedListArrayList
底层结构双向链表动态数组
随机访问(get(i))❌ O(n)✅ O(1)
尾部 add/remove✅ O(1)✅ O(1)(均摊)
头部 add/remove✅ O(1)❌ O(n)(需移动所有元素)
中间插入/删除✅ O(1)(定位后
❌ O(n)(含定位时间
❌ O(n)
内存占用较大(每个节点存 prev/next + 对象头)较小(仅数组)
CPU 缓存友好性❌ 差(内存分散)✅ 好(连续内存)
是否实现 Deque✅ 是❌ 否
默认初始容量无概念0(首次 add 扩到 10)
扩容机制无需扩容1.5 倍扩容

💡 关键结论

  • 如果你频繁通过索引访问 → 选 ArrayList
  • 如果你频繁在首尾操作(如队列/栈)→ 选 LinkedList
  • 如果你频繁在中间插入/删除 → 两者都慢!(因定位需 O(n)),此时应考虑是否设计合理
性能测试对比(10 万次操作):
操作ArrayListLinkedList
尾部 add✅ 极快✅ 极快
头部 add❌ 极慢(移动全部)✅ 极快
get(50000)✅ 瞬间❌ 需遍历一半
遍历(for-each)✅ 快(缓存友好)❌ 慢(指针跳转)

📌 实测结论:除非明确需要首尾高效操作,否则 ArrayList 综合性能更优!

1.6 高频面试题(面经精要)

Q1:LinkedList 的底层结构是什么?

:基于双向链表,每个节点包含数据、前驱指针(prev)和后继指针(next),并维护 firstlast 指向首尾节点。


Q2:LinkedList 查询为什么慢?

:因为链表不支持随机访问,必须从头或尾开始遍历才能定位到指定索引,时间复杂度为 O(n)。


Q3:LinkedList 和 ArrayList 如何选择?

  • 读多写少、需随机访问 → ArrayList
  • 频繁在首尾增删(如队列/栈) → LinkedList
  • 一般场景默认选 ArrayList,因其缓存友好、综合性能更好

Q4:LinkedList 可以当栈或队列用吗?

:可以!因为它实现了 Deque 接口,支持:

  • push(e) / pop() / peek()
  • 队列offer(e) / poll() / peek()

⚠️ 但注意:Java 官方文档建议优先使用 ArrayDeque 实现栈/队列,性能优于 LinkedList


Q5:为什么 LinkedList 没有像 ArrayList 那样的初始容量?

:因为链表是动态链接节点,不需要预先分配连续内存空间,每次 add 只需创建一个新节点,因此没有“容量”概念。


Q6:LinkedList 的内存开销比 ArrayList 大多少?

:每个元素额外占用:

  • 两个引用(prev + next):64 位 JVM 上约 16 字节
  • 对象头(Node 对象本身):约 12~16 字节
  • 总计每个元素多消耗 ~32 字节,远高于 ArrayList 的纯数组存储。

声明:

分析借鉴于通义AI

以上均来源于B站@ITheima的教学内容!!!

本人跟着视频内容学习,整理知识引用

http://www.dtcms.com/a/585337.html

相关文章:

  • CUDA C++编程指南(3.2.7)——内存同步域
  • 超市进销存源码
  • 100G相机接口
  • Linux第一个小程序 之 【进度条】
  • 网站建设的目的及功能定位网站建设落地页
  • CentOS 7 上安装 MySQL 8.0
  • LLM 训练基础概念与流程简介
  • HTML 音频(Audio)详解
  • 认识BUG~
  • RV1126 NO.44:OPENCV的cvtColor和putText的讲解
  • HTTP-发展史
  • AI 编程工具Claude Code 介绍
  • 2022年IEEE TITS SCI2区TOP,基于切线交点和目标引导策略的无人机自主路径规划,深度解析+性能实测
  • 旧电脑系统无损迁移至新电脑、硬盘系统克隆完整教程
  • 长沙网站seo诊断佛山做网站企业
  • 识别和破除信息茧房
  • 超时重传 vs 快速重传:TCP双保险如何拯救网络丢包?
  • 余弦相似度:衡量向量空间方向一致性的核心度量
  • 好网站建设公司报价文字类wordpress主题
  • 【科研绘图系列】R语言绘制密度分布图(density plot)
  • R语言绘图与可视化第六章总结
  • 建设工程消防设计备案网站网络服务提供者收集和使用
  • 如何在自己的服务器上部署 n8n
  • LangChain提示词模版 PromptTemplate
  • 做国外购物的网站怎么发货网站建设中期怎么入账
  • 【安全开发】Nuclei源码分析-模板引擎实现(五)
  • 【小技巧】PyCharm建立项目,VScode+CodeX+WindowsPowerShell开发Python pyQT6 (二)
  • 办个人网站租空间餐饮网站建设的模板
  • 国家开发投资集团有限公司广州新站优化
  • MySQL数据类型详解