本系列可作为JAVA学习系列的笔记,文中提到的一些练习的代码,小编会将代码复制下来,大家复制下来就可以练习了,方便大家学习。
点赞关注不迷路!您的点赞、关注和收藏是对小编最大的支持和鼓励!
系列文章目录
JAVA初阶----------------------------------已更完
目录
目录
[一、Java 集合框架:数据存储与操作的基石](#一、Java 集合框架:数据存储与操作的基石)
[1.1 什么是集合框架](#1.1 什么是集合框架)
[1.1.1 集合框架的核心结构](#1.1.1 集合框架的核心结构)
[1.1.2 核心组件解析](#1.1.2 核心组件解析)
[1.2 集合框架的重要性:开发与面试双视角](#1.2 集合框架的重要性:开发与面试双视角)
[1.2.1 开发中的核心价值](#1.2.1 开发中的核心价值)
[1.2.2 面试中的高频考点](#1.2.2 面试中的高频考点)
[1.3 集合框架背后的数据结构](#1.3 集合框架背后的数据结构)
[1.3.1 数据结构基础概念](#1.3.1 数据结构基础概念)
[1.3.2 核心集合与数据结构的对应关系](#1.3.2 核心集合与数据结构的对应关系)
[1.3.3 集合框架依赖的 Java 基础知识](#1.3.3 集合框架依赖的 Java 基础知识)
[二、Collection 体系:单列集合的实现与实战](#二、Collection 体系:单列集合的实现与实战)
[2.1 Collection 接口:单列集合的根接口](#2.1 Collection 接口:单列集合的根接口)
[2.2 List 接口:有序可重复的集合](#2.2 List 接口:有序可重复的集合)
[2.2.1 ArrayList:动态数组的实现](#2.2.1 ArrayList:动态数组的实现)
[2.2.2 LinkedList:双向链表的实现](#2.2.2 LinkedList:双向链表的实现)
[2.2.3 List 实现类对比](#2.2.3 List 实现类对比)
[2.3 Set 接口:无序不可重复的集合](#2.3 Set 接口:无序不可重复的集合)
[2.3.1 HashSet:哈希表的实现](#2.3.1 HashSet:哈希表的实现)
[2.3.2 TreeSet:红黑树的实现](#2.3.2 TreeSet:红黑树的实现)
[2.3.3 Set 实现类对比](#2.3.3 Set 实现类对比)
[2.4 Queue 接口:队列结构的实现](#2.4 Queue 接口:队列结构的实现)
[2.4.1 PriorityQueue:优先级队列](#2.4.1 PriorityQueue:优先级队列)
[2.4.2 ArrayDeque:双端队列](#2.4.2 ArrayDeque:双端队列)
[三、Map 体系:键值对映射的实现与实战](#三、Map 体系:键值对映射的实现与实战)
[3.1 Map 接口:双列集合的根接口](#3.1 Map 接口:双列集合的根接口)
[3.2 HashMap:哈希表的实现](#3.2 HashMap:哈希表的实现)
[3.2.1 底层原理(JDK1.8+)](#3.2.1 底层原理(JDK1.8+))
[3.2.2 核心参数与扩容机制](#3.2.2 核心参数与扩容机制)
[3.2.3 核心方法与使用示例](#3.2.3 核心方法与使用示例)
[3.2.4 Key 的注意事项](#3.2.4 Key 的注意事项)
[3.2.5 性能特点与适用场景](#3.2.5 性能特点与适用场景)
[3.3 TreeMap:红黑树的实现](#3.3 TreeMap:红黑树的实现)
[3.3.1 底层原理](#3.3.1 底层原理)
[3.3.2 核心方法与使用示例](#3.3.2 核心方法与使用示例)
[3.3.3 性能特点与适用场景](#3.3.3 性能特点与适用场景)
[3.4 Map 实现类对比](#3.4 Map 实现类对比)
[3.5 线程安全的集合方案](#3.5 线程安全的集合方案)
[3.5.1 Collections 工具类包装](#3.5.1 Collections 工具类包装)
[3.5.2 JUC 包中的线程安全集合](#3.5.2 JUC 包中的线程安全集合)
[4.1 算法效率的衡量标准](#4.1 算法效率的衡量标准)
[4.2 时间复杂度](#4.2 时间复杂度)
[4.2.1 时间复杂度的定义](#4.2.1 时间复杂度的定义)
[4.2.2 大 O 渐进表示法](#4.2.2 大 O 渐进表示法)
[(1)推导大 O 阶的三步法](#(1)推导大 O 阶的三步法)
[4.2.3 常见时间复杂度及示例](#4.2.3 常见时间复杂度及示例)
[(1)O (1):常数阶](#(1)O (1):常数阶)
[(2)O (logN):对数阶](#(2)O (logN):对数阶)
[(3)O (N):线性阶](#(3)O (N):线性阶)
[(4)O (NlogN):线性对数阶](#(4)O (NlogN):线性对数阶)
[(5)O (N²):平方阶](#(5)O (N²):平方阶)
[(6)O (2ⁿ):指数阶](#(6)O (2ⁿ):指数阶)
[4.2.4 最好、平均、最坏情况](#4.2.4 最好、平均、最坏情况)
[4.3 空间复杂度](#4.3 空间复杂度)
[4.3.1 空间复杂度的定义](#4.3.1 空间复杂度的定义)
[4.3.2 常见空间复杂度及示例](#4.3.2 常见空间复杂度及示例)
[(1)O (1):常数阶](#(1)O (1):常数阶)
[(2)O (N):线性阶](#(2)O (N):线性阶)
[(3)O (logN):对数阶](#(3)O (logN):对数阶)
[(4)O (N²):平方阶](#(4)O (N²):平方阶)
[4.3.3 空间复杂度与时间复杂度的区别](#4.3.3 空间复杂度与时间复杂度的区别)
[4.4 复杂度分析实战示例](#4.4 复杂度分析实战示例)
[4.4.1 示例 1:冒泡排序的复杂度分析](#4.4.1 示例 1:冒泡排序的复杂度分析)
[4.4.2 示例 2:递归阶乘的复杂度分析](#4.4.2 示例 2:递归阶乘的复杂度分析)
[4.4.3 示例 3:斐波那契数列(递归)的复杂度分析](#4.4.3 示例 3:斐波那契数列(递归)的复杂度分析)
[5.1 集合框架选择指南](#5.1 集合框架选择指南)
[5.2 算法复杂度优化建议](#5.2 算法复杂度优化建议)
[5.3 学习路径建议](#5.3 学习路径建议)
前言
小编作为新晋码农一枚,会定期整理一些写的比较好的代码,作为自己的学习笔记,会试着做一下批注和补充,如转载或者参考他人文献会标明出处,非商用,如有侵权会删改!欢迎大家斧正和讨论!
一、Java 集合框架:数据存储与操作的基石
1.1 什么是集合框架
Java 集合框架(Java Collections Framework,简称 JCF)是定义在java.util包下的一组接口(Interfaces)与实现类(Classes)的总称,也被称为 "容器"(Container)。它的核心作用是将多个元素(Element)封装在一个单元中,提供高效的存储(Store)、检索(Retrieve)、管理(Manipulate)能力,也就是我们常说的增删查改(CRUD)操作。
从现实场景来看,集合框架就像生活中的 "容器":一副扑克牌是牌的集合,一个邮箱是邮件的集合,一个通讯录是姓名与电话的映射集合。在程序开发中,我们需要处理大量类似的 "批量数据",集合框架正是为解决这类问题而生。
1.1.1 集合框架的核心结构
集合框架的整体架构围绕两大核心体系展开,通过接口与实现类的分离设计,实现了 "面向接口编程" 的思想,具体结构如下:
java
java.util包核心结构
├─ Iterable(迭代器接口,所有集合的顶层父接口)
│ └─ Collection(单列集合根接口,存储单个元素)
│ ├─ List(有序、可重复、支持索引)
│ │ ├─ ArrayList(动态数组实现)
│ │ ├─ LinkedList(双向链表实现)
│ │ ├─ Vector(线程安全的动态数组,已过时)
│ │ └─ Stack(栈结构,继承Vector,已过时)
│ ├─ Set(无序、不可重复、无索引)
│ │ ├─ HashSet(哈希表实现,基于HashMap)
│ │ ├─ LinkedHashSet(哈希表+双向链表,保证插入顺序)
│ │ └─ TreeSet(红黑树实现,保证元素有序)
│ └─ Queue(队列,遵循FIFO原则)
│ ├─ LinkedList(实现Queue接口,双端队列)
│ ├─ PriorityQueue(优先级队列,基于堆实现)
│ └─ Deque(双端队列接口)
│ └─ ArrayDeque(动态数组实现的双端队列)
└─ Map(双列集合根接口,存储键值对K-V)
├─ HashMap(哈希表实现,线程不安全)
├─ LinkedHashMap(哈希表+双向链表,保证顺序)
├─ TreeMap(红黑树实现,Key有序)
├─ Hashtable(线程安全的哈希表,已过时)
└─ ConcurrentHashMap(线程安全的HashMap,支持高并发)
1.1.2 核心组件解析
集合框架包含三大核心组件,这三大组件共同构成了完整的 "数据存储 - 操作 - 算法" 体系:
- 接口(Interfaces) :定义集合的抽象行为,规定 "能做什么",不关心 "怎么做"。例如
List接口规定了有序、可重复的特性,Set接口规定了无序、不可重复的特性。 - 实现类(Classes) :接口的具体实现,对应底层的数据结构,解决 "怎么做" 的问题。例如
ArrayList用动态数组实现List接口,HashSet用哈希表实现Set接口。 - 算法(Algorithms) :定义在
Collections和Arrays工具类中的静态方法,提供排序、查找、填充等通用操作。例如Collections.sort()用于集合排序,Arrays.binarySearch()用于数组二分查找。
1.2 集合框架的重要性:开发与面试双视角
1.2.1 开发中的核心价值
在实际开发中,集合框架是不可或缺的工具,其价值主要体现在三个方面:
- 提升开发效率 :无需手动实现复杂的数据结构(如链表、哈希表),框架已提供经过严格测试、高度优化的实现。例如需要存储动态列表时,直接使用
ArrayList即可,无需自己处理数组扩容。 - 保证代码质量 :框架实现类由 Java 官方团队开发,性能与稳定性远超大多数开发者临时编写的代码。例如
HashMap的哈希冲突解决、扩容机制等,经过了多版本迭代优化。 - 增强代码灵活性 :基于接口的设计让代码具备良好的扩展性。例如方法参数定义为
List接口,调用者可传入ArrayList或LinkedList,无需修改方法内部逻辑。
1.2.2 面试中的高频考点
集合框架是 Java 面试的 "必考题",几乎所有 Java 后台开发岗位都会涉及。从各大厂的面经来看,核心考点集中在以下方向:
- 腾讯 Java 后台开发面经 :HashMap 实现原理、对象作为 Key 时
hashCode()与equals()的注意事项、HashSet 与 HashMap 的区别、线程安全的集合方案。 - 阿里巴巴 Java 后台开发面经:ArrayList 与 LinkedList 的区别、HashMap 的具体实现、HashMap 与 ConcurrentHashMap 的效率对比。
- 今日头条 Java 后台开发面经:Redis 的 zset 对应 Java 中的集合类型、hashCode () 的作用、集合相关的编程题(如回文链表判断)。
这些问题本质上都是考察对 "集合底层数据结构" 与 "实现原理" 的理解,而非简单的 API 调用。
1.3 集合框架背后的数据结构
集合框架的每个实现类,本质上都是对某种特定数据结构的封装。理解底层数据结构,是掌握集合框架的关键。
1.3.1 数据结构基础概念
数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。常见的数据结构包括数组、链表、栈、队列、哈希表、树等,它们各自有不同的特性与适用场景。
1.3.2 核心集合与数据结构的对应关系
| 集合类 | 底层数据结构 | 核心特性 | 时间复杂度(增删查) |
|---|---|---|---|
| ArrayList | 动态数组(Object []) | 有序、可重复、支持索引 | 查 O (1),增删 O (n) |
| LinkedList | 双向链表 | 有序、可重复、支持首尾快速操作 | 查 O (n),增删 O (1)(定位后) |
| HashSet | 哈希表(基于 HashMap) | 无序、不可重复、无索引 | 增删查 O (1)(平均) |
| TreeSet | 红黑树 | 有序(自然排序)、不可重复 | 增删查 O (log₂N) |
| HashMap | 数组 + 链表 + 红黑树(JDK1.8+) | 无序、Key 唯一、Value 可重复 | 增删查 O (1)(平均) |
| TreeMap | 红黑树 | Key 有序(自然排序)、Key 唯一 | 增删查 O (log₂N) |
| PriorityQueue | 堆(完全二叉树) | 优先级排序、队列结构 | 入队 O (log₂N),出队 O (log₂N) |
| ArrayDeque | 动态数组(循环数组) | 双端队列、支持首尾操作 | 增删查 O (1) |
1.3.3 集合框架依赖的 Java 基础知识
要熟练使用集合框架,需要掌握以下 Java 核心知识点:
- 泛型(Generic) :解决集合的类型安全问题,避免强制类型转换。例如
List<String>明确集合存储 String 类型,编译期即可检查类型错误。 - 自动装箱(Autobox)与拆箱(Autounbox) :基本类型与包装类的自动转换。例如
list.add(1)会自动将 int 转为 Integer,int num = list.get(0)会自动将 Integer 转为 int。 - Object 的 equals () 方法 :判断对象是否相等,是 Set 去重、Map 键唯一的核心依据。自定义对象存入集合时,必须重写
equals()保证逻辑正确性。 - Comparable 与 Comparator 接口 :实现对象的排序。
Comparable是 "自然排序"(如 String 的字典序),Comparator是 "自定义排序"(如按字符串长度排序)。
二、Collection 体系:单列集合的实现与实战
2.1 Collection 接口:单列集合的根接口
Collection是所有单列集合的顶层接口,定义了操作元素的通用方法,这些方法被所有子接口(List、Set、Queue)继承,具体包括:
boolean add(E e):添加元素,成功返回 trueboolean remove(Object o):删除指定元素,成功返回 trueint size():返回集合元素个数boolean isEmpty():判断集合是否为空void clear():清空集合Iterator<E> iterator():获取迭代器,用于遍历集合
需要注意的是,Collection本身是抽象接口,无法直接实例化,必须通过其子接口的实现类使用。
2.2 List 接口:有序可重复的集合
List接口是Collection的子接口,核心特性是有序 (元素存取顺序一致)、可重复 (允许存储相同元素)、支持索引(通过下标访问元素),类似数组的 "增强版"。
2.2.1 ArrayList:动态数组的实现
(1)底层原理
ArrayList基于动态数组 (Object[])实现,JDK1.8 及以上版本中,默认初始容量为 10。当元素数量超过数组容量时,会触发扩容机制:
- 扩容公式:新容量 = 旧容量 + 旧容量右移 1 位(即旧容量的 1.5 倍)
- 扩容过程:创建新数组 → 将原数组元素拷贝到新数组 → 指向新数组
- 例如:初始容量 10 → 扩容后 15 → 再次扩容后 22(15+7=22)
(2)核心方法与使用示例
java
// 创建ArrayList集合(指定泛型为String)
List<String> list = new ArrayList<>();
// 1. 添加元素
list.add("Java"); // 末尾添加,返回true
list.add(1, "Python"); // 索引1插入,后续元素后移
System.out.println(list); // 输出:[Java, Python]
// 2. 获取元素
String first = list.get(0); // 获取索引0元素,返回"Java"
// 3. 修改元素
list.set(0, "Go"); // 将索引0元素改为"Go"
System.out.println(list); // 输出:[Go, Python]
// 4. 删除元素
list.remove(1); // 删除索引1元素,返回"Python"
System.out.println(list); // 输出:[Go]
// 5. 遍历集合(三种方式)
// 方式1:for循环(利用索引)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 方式2:增强for循环
for (String elem : list) {
System.out.println(elem);
}
// 方式3:迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String elem = iterator.next();
System.out.println(elem);
}
(3)性能特点与适用场景
- 优点:随机访问效率极高(时间复杂度 O (1)),通过数组下标直接定位元素;遍历速度快,适合批量读取。
- 缺点:插入 / 删除元素效率低(时间复杂度 O (n)),尤其是中间位置的操作,需要移动后续所有元素;扩容时会产生内存开销,可能浪费空间。
- 适用场景 :读多写少的场景,例如数据展示列表、高频读取的配置缓存、数据库查询结果存储等。
(4)注意事项
- 初始容量优化 :如果预知元素数量,创建时指定容量可减少扩容次数。例如
new ArrayList<>(100),避免多次数组拷贝。 - 线程安全问题 :
ArrayList是非线程安全的,多线程环境下并发修改会抛出ConcurrentModificationException。解决方案包括:- 使用
Collections.synchronizedList(list)包装 - 使用线程安全的替代类
CopyOnWriteArrayList
- 使用
- 避免频繁扩容:频繁扩容会导致性能下降,建议根据业务场景预估容量。
2.2.2 LinkedList:双向链表的实现
(1)底层原理
LinkedList基于双向链表 实现,每个节点(Node)包含三个部分:
prev:前驱指针,指向前一个节点item:节点存储的元素next:后继指针,指向后一个节点
JDK1.8 取消了循环链表设计,链表首尾节点的prev和next分别指向null。通过指针的指向关系,实现元素的存储与访问。
(2)核心方法与使用示例
java
// 创建LinkedList集合
LinkedList<String> list = new LinkedList<>();
// 1. 首尾添加元素
list.addFirst("Head"); // 头部添加
list.addLast("Tail"); // 尾部添加
System.out.println(list); // 输出:[Head, Tail]
// 2. 首尾获取元素
String first = list.getFirst(); // 获取头部元素,返回"Head"
String last = list.getLast(); // 获取尾部元素,返回"Tail"
// 3. 首尾删除元素
list.removeFirst(); // 删除头部元素,返回"Head"
list.removeLast(); // 删除尾部元素,返回"Tail"
System.out.println(list); // 输出:[]
// 4. 作为队列使用(FIFO)
list.offer("A"); // 尾部添加(队列常用方法)
list.offer("B");
System.out.println(list.poll()); // 头部删除,返回"A"
// 5. 作为栈使用(LIFO)
list.push("C"); // 头部添加(栈常用方法)
list.push("D");
System.out.println(list.pop()); // 头部删除,返回"D"
(3)性能特点与适用场景
- 优点:插入 / 删除效率高(定位节点后时间复杂度 O (1)),仅需修改相邻节点的指针;支持首尾快速操作,适合队列、栈等场景。
- 缺点 :随机访问效率低(时间复杂度 O (n)),获取指定索引元素需从链表头或尾遍历;内存开销略高于
ArrayList,每个元素需额外存储两个指针。 - 适用场景 :写多读少的场景,例如频繁插入 / 删除的列表、队列(FIFO)、栈(LIFO)的实现、消息队列的临时存储等。
2.2.3 List 实现类对比
| 对比维度 | ArrayList | LinkedList | Vector(已过时) |
|---|---|---|---|
| 底层结构 | 动态数组 | 双向链表 | 动态数组 |
| 线程安全 | 否 | 否 | 是(方法加 synchronized) |
| 随机访问效率 | 高(O (1)) | 低(O (n)) | 高(O (1)),但性能低 |
| 插入 / 删除效率 | 低(O (n)) | 高(O (1),定位后) | 低(O (n)),且性能低 |
| 扩容机制 | 1.5 倍扩容 | 无需扩容 | 2 倍扩容 |
| 内存开销 | 较低(仅存储元素) | 较高(额外存储指针) | 较高(可能浪费更多空间) |
| 适用场景 | 读多写少 | 写多读少 | 老旧系统兼容 |
2.3 Set 接口:无序不可重复的集合
Set接口是Collection的另一子接口,核心特性是无序 (存储顺序与插入顺序不一致,LinkedHashSet 除外)、不可重复 (元素唯一)、无索引(不支持下标访问)。
2.3.1 HashSet:哈希表的实现
(1)底层原理
HashSet基于HashMap实现,内部持有一个HashMap实例,元素存储在HashMap的Key中,Value固定为一个静态空对象(PRESENT)。其核心机制是通过哈希表保证元素唯一性:
- 计算元素的
hashCode()值,确定元素在哈希表中的存储位置(哈希桶)。 - 若该位置无元素,直接存入;若有元素,通过
equals()方法判断是否相同:- 相同:不存入(保证唯一性)
- 不同:通过链表或红黑树解决哈希冲突(JDK1.8 后,链表长度超过 8 且数组容量≥64 时,链表转为红黑树)。
(2)核心方法与使用示例
java
// 创建HashSet集合
Set<String> set = new HashSet<>();
// 1. 添加元素(重复元素不会被添加)
boolean add1 = set.add("Apple"); // 返回true
boolean add2 = set.add("Banana"); // 返回true
boolean add3 = set.add("Apple"); // 重复元素,返回false
System.out.println(set); // 输出:[Banana, Apple](无序)
// 2. 删除元素
boolean remove = set.remove("Banana"); // 返回true
System.out.println(set); // 输出:[Apple]
// 3. 判断元素是否存在
boolean contains = set.contains("Apple"); // 返回true
// 4. 遍历集合(无索引,仅支持增强for和迭代器)
// 方式1:增强for
for (String elem : set) {
System.out.println(elem);
}
// 方式2:迭代器
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
(3)元素唯一性的保证
HashSet判断元素唯一性依赖两个方法:hashCode()和equals(),必须同时满足以下规则:
- 若两个元素的
hashCode()值不同 → 元素不同,直接存入不同哈希桶。 - 若两个元素的
hashCode()值相同 → 需调用equals()判断:equals()返回 true → 元素相同,不存入。equals()返回 false → 元素不同,存入同一哈希桶(哈希冲突)。
因此,自定义对象存入HashSet时,必须重写hashCode()和equals()方法,否则会导致重复元素无法被识别。
(4)性能特点与适用场景
- 优点:增删查平均时间复杂度 O (1),效率极高;支持快速去重。
- 缺点:元素无序,迭代顺序与插入顺序无关;不支持索引访问。
- 适用场景:无需保留顺序的去重场景,例如存储用户 ID 集合、标签集合、不重复的配置项等。
2.3.2 TreeSet:红黑树的实现
(1)底层原理
TreeSet基于红黑树 (一种自平衡的二叉搜索树)实现,核心特性是元素有序 (自然排序或自定义排序)。其底层依赖TreeMap,元素存储在TreeMap的Key中,通过红黑树的特性保证元素有序且唯一。
红黑树的特点是:
- 左子树所有节点值 < 根节点值。
- 右子树所有节点值 > 根节点值。
- 任意节点的左右子树高度差不超过 1,保证查询效率为 O (log₂N)。
(2)核心方法与使用示例
java
// 1. 自然排序(String实现了Comparable接口,按字典序排序)
Set<String> treeSet = new TreeSet<>();
treeSet.add("Banana");
treeSet.add("Apple");
treeSet.add("Cherry");
System.out.println(treeSet); // 输出:[Apple, Banana, Cherry]
// 2. 自定义排序(按字符串长度排序)
Set<String> customSet = new TreeSet<>((a, b) -> a.length() - b.length());
customSet.add("Java"); // 长度4
customSet.add("Go"); // 长度2
customSet.add("Python"); // 长度6
System.out.println(customSet); // 输出:[Go, Java, Python]
// 3. 有序相关方法
TreeSet<String> sortedSet = new TreeSet<>();
sortedSet.add("A");
sortedSet.add("B");
sortedSet.add("C");
System.out.println(sortedSet.first()); // 获取第一个元素,返回"A"
System.out.println(sortedSet.last()); // 获取最后一个元素,返回"C"
System.out.println(sortedSet.subSet("A", "C")); // 获取[A, C)区间元素,返回[A, B]
(3)排序规则的实现
TreeSet的有序性依赖两种方式:
- 自然排序 :元素实现
Comparable接口,重写compareTo()方法。例如String、Integer等内置类已实现Comparable,可直接排序。 - 自定义排序 :创建
TreeSet时传入Comparator接口实现类,定义排序逻辑。例如按字符串长度、自定义对象的某个字段排序。
若元素既未实现Comparable,也未指定Comparator,添加元素时会抛出ClassCastException。
(4)性能特点与适用场景
- 优点 :元素有序,支持范围查询(如
subSet());增删查时间复杂度 O (log₂N),适合有序场景。 - 缺点 :效率低于
HashSet;元素必须支持排序(实现Comparable或指定Comparator)。 - 适用场景:需要有序且去重的场景,例如按价格排序的商品集合、按时间排序的日志 ID 集合、范围查询的数据集等。
2.3.3 Set 实现类对比
| 对比维度 | HashSet | LinkedHashSet | TreeSet |
|---|---|---|---|
| 底层结构 | 哈希表(基于 HashMap) | 哈希表 + 双向链表 | 红黑树(基于 TreeMap) |
| 元素顺序 | 无序(哈希顺序) | 有序(插入顺序) | 有序(自然 / 自定义排序) |
| 元素唯一性 | 依赖 hashCode ()+equals () | 依赖 hashCode ()+equals () | 依赖 compareTo ()/compare () |
| 增删查效率 | O (1)(平均) | O (1)(平均,略低于 HashSet) | O(log₂N) |
| 线程安全 | 否 | 否 | 否 |
| 支持 null 元素 | 是(仅一个) | 是(仅一个) | 否 |
| 适用场景 | 无序去重 | 有序去重(插入顺序) | 有序去重(排序需求) |
2.4 Queue 接口:队列结构的实现
Queue接口是Collection的子接口,遵循FIFO(先进先出) 原则,主要用于实现队列、双端队列等数据结构。
2.4.1 PriorityQueue:优先级队列
(1)底层原理
PriorityQueue基于堆 (完全二叉树)实现,核心特性是元素按优先级排序 ,出队时始终返回优先级最高的元素(而非插入顺序)。默认按 "自然排序"(升序),也可通过Comparator指定自定义优先级。
堆的特点是:
- 小根堆(默认):父节点值 ≤ 子节点值,根节点是最小值。
- 大根堆(自定义):父节点值 ≥ 子节点值,根节点是最大值。
(2)核心方法与使用示例
java
// 1. 自然排序(Integer按升序,小根堆)
Queue<Integer> pq1 = new PriorityQueue<>();
pq1.offer(3);
pq1.offer(1);
pq1.offer(2);
System.out.println(pq1.poll()); // 出队最小值1
System.out.println(pq1.poll()); // 出队次小值2
// 2. 自定义排序(按降序,大根堆)
Queue<Integer> pq2 = new PriorityQueue<>((a, b) -> b - a);
pq2.offer(3);
pq2.offer(1);
pq2.offer(2);
System.out.println(pq2.poll()); // 出队最大值3
System.out.println(pq2.poll()); // 出队次大值2
// 3. 自定义对象排序(按年龄升序)
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + "(" + age + ")";
}
}
Queue<Person> pq3 = new PriorityQueue<>((a, b) -> a.age - b.age);
pq3.offer(new Person("张三", 25));
pq3.offer(new Person("李四", 20));
pq3.offer(new Person("王五", 30));
System.out.println(pq3.poll()); // 输出:李四(20)
(3)性能特点与适用场景
- 优点:优先级排序,出队始终返回最高优先级元素;入队 / 出队时间复杂度 O (log₂N)。
- 缺点:元素无序(不按插入顺序);不支持 null 元素;非线程安全。
- 适用场景:需要按优先级处理任务的场景,例如任务调度(高优先级任务先执行)、TOP-K 问题(取前 K 个最大值 / 最小值)、事件驱动的消息处理等。
2.4.2 ArrayDeque:双端队列
(1)底层原理
ArrayDeque基于循环数组 实现,支持双端操作(首尾均可添加 / 删除元素),可同时作为队列(FIFO)和栈(LIFO)使用。其核心优势是:
- 循环数组:数组下标循环使用,避免元素移动,提高效率。
- 动态扩容:容量不足时,扩容为 2 倍(若原容量 < 64)或 1.5 倍(若原容量≥64)。
(2)核心方法与使用示例
java
// 创建ArrayDeque(双端队列)
Deque<String> deque = new ArrayDeque<>();
// 1. 作为队列使用(FIFO)
deque.offer("A"); // 尾部添加
deque.offer("B");
System.out.println(deque.poll()); // 头部删除,返回"A"
// 2. 作为栈使用(LIFO)
deque.push("C"); // 头部添加
deque.push("D");
System.out.println(deque.pop()); // 头部删除,返回"D"
// 3. 双端操作
deque.addFirst("Head"); // 头部添加
deque.addLast("Tail"); // 尾部添加
System.out.println(deque.getFirst()); // 获取头部,返回"Head"
System.out.println(deque.getLast()); // 获取尾部,返回"Tail"
deque.removeFirst(); // 删除头部
deque.removeLast(); // 删除尾部
(3)性能特点与适用场景
- 优点 :首尾操作效率 O (1);支持队列与栈两种模式;比
LinkedList更高效(数组比链表缓存命中率高)。 - 缺点:不支持索引访问;非线程安全。
- 适用场景:需要双端操作的场景,例如队列、栈的实现、滑动窗口算法、高频首尾操作的缓存等。
三、Map 体系:键值对映射的实现与实战
3.1 Map 接口:双列集合的根接口
Map接口是与Collection并列的核心接口,用于存储键值对(Key-Value) 映射关系。其核心特性是:
- Key 唯一:同一个 Map 中,Key 不能重复(重复添加会覆盖 Value)。
- Value 可重复:不同 Key 可对应相同 Value。
- 无索引:不支持下标访问,需通过 Key 获取 Value。
Map接口的核心方法包括:
V put(K key, V value):添加键值对,返回旧 Value(无则返回 null)。V get(Object key):通过 Key 获取 Value,无则返回 null。V remove(Object key):通过 Key 删除键值对,返回删除的 Value。Set<K> keySet():获取所有 Key 的集合。Collection<V> values():获取所有 Value 的集合。Set<Map.Entry<K, V>> entrySet():获取所有键值对的集合(推荐遍历方式)。
3.2 HashMap:哈希表的实现
HashMap是Map接口最常用的实现类,基于哈希表(数组 + 链表 + 红黑树)实现,JDK1.8 对其进行了重大优化,解决了 JDK1.7 中链表过长导致的查询效率低下问题。
3.2.1 底层原理(JDK1.8+)
HashMap的底层结构是 "数组 + 链表 + 红黑树" 的组合,具体结构如下:
- 数组(哈希桶) :默认初始容量 16,存储链表或红黑树的头节点。数组下标通过 Key 的
hashCode()计算得出:index = (容量-1) & hash。 - 链表:当多个 Key 映射到同一哈希桶时,通过链表存储(哈希冲突解决)。当链表长度超过 8 且数组容量≥64 时,链表转为红黑树。
- 红黑树:当链表长度超过阈值(8)时转为红黑树,将查询时间复杂度从 O (n) 优化为 O (log₂N)。当红黑树节点数少于 6 时,转回链表。
3.2.2 核心参数与扩容机制
HashMap的性能与三个核心参数密切相关:
- 初始容量:默认 16,必须是 2 的幂(便于通过位运算计算下标)。
- 负载因子:默认 0.75,扩容阈值 = 容量 × 负载因子(例如 16×0.75=12)。当元素数量超过阈值时,触发扩容。
- 扩容机制:每次扩容为原容量的 2 倍,重新计算所有 Key 的哈希值与下标,将元素迁移到新数组。
3.2.3 核心方法与使用示例
java
// 创建HashMap
Map<String, Integer> map = new HashMap<>();
// 1. 添加键值对(重复Key会覆盖Value)
map.put("Java", 90);
map.put("Python", 85);
map.put("Java", 95); // 覆盖原Value,返回90
System.out.println(map); // 输出:{Java=95, Python=85}
// 2. 获取Value
Integer javaScore = map.get("Java"); // 返回95
Integer goScore = map.get("Go"); // 无此Key,返回null
// 3. 删除键值对
Integer removedScore = map.remove("Python"); // 返回85
System.out.println(map); // 输出:{Java=95}
// 4. 遍历Map(三种方式)
// 方式1:遍历Key,再获取Value(效率低,需多次get())
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println(key + ":" + map.get(key));
}
// 方式2:直接遍历Value(无法获取Key)
Collection<Integer> values = map.values();
for (Integer value : values) {
System.out.println(value);
}
// 方式3:遍历键值对(推荐,效率高)
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
3.2.4 Key 的注意事项
HashMap的 Key 需满足与HashSet相同的规则:
- 重写
hashCode()和equals():保证 Key 的唯一性,避免哈希冲突导致的重复存储。 - Key 不可变:推荐使用不可变类(如 String、Integer)作为 Key,若 Key 是可变对象,修改后可能导致无法找到 Value。
3.2.5 性能特点与适用场景
- 优点:增删查平均时间复杂度 O (1),效率极高;支持 null Key 和 null Value(仅一个 null Key)。
- 缺点:Key 无序;非线程安全,多线程并发修改可能导致死循环(JDK1.7)或数据不一致(JDK1.8)。
- 适用场景:无需 Key 有序、需要高效键值对映射的场景,例如缓存存储、配置映射、用户信息存储等。
3.3 TreeMap:红黑树的实现
TreeMap基于红黑树 实现,实现了SortedMap接口,核心特性是Key 有序(自然排序或自定义排序),适用于需要按 Key 排序的场景。
3.3.1 底层原理
TreeMap的底层红黑树与TreeSet一致,Key 的排序通过两种方式实现:
- 自然排序 :Key 实现
Comparable接口,重写compareTo()方法(如 String、Integer)。 - 自定义排序 :创建
TreeMap时传入Comparator接口,定义 Key 的排序逻辑。
红黑树的自平衡特性保证了 Key 的有序性,同时确保增删查时间复杂度为 O (log₂N)。
3.3.2 核心方法与使用示例
java
// 1. 自然排序(String按字典序,Key有序)
Map<String, Integer> treeMap1 = new TreeMap<>();
treeMap1.put("Banana", 80);
treeMap1.put("Apple", 90);
treeMap1.put("Cherry", 85);
System.out.println(treeMap1); // 输出:{Apple=90, Banana=80, Cherry=85}
// 2. 自定义排序(Key按长度排序)
Map<String, Integer> treeMap2 = new TreeMap<>((a, b) -> a.length() - b.length());
treeMap2.put("Java", 95);
treeMap2.put("Go", 85);
treeMap2.put("Python", 90);
System.out.println(treeMap2); // 输出:{Go=85, Java=95, Python=90}
// 3. 有序相关方法
SortedMap<String, Integer> sortedMap = new TreeMap<>();
sortedMap.put("A", 1);
sortedMap.put("B", 2);
sortedMap.put("C", 3);
System.out.println(sortedMap.firstKey()); // 获取第一个Key,返回"A"
System.out.println(sortedMap.lastKey()); // 获取最后一个Key,返回"C"
System.out.println(sortedMap.subMap("A", "C")); // 获取[A, C)区间,返回{A=1, B=2}
3.3.3 性能特点与适用场景
- 优点:Key 有序,支持范围查询;Key 唯一,基于排序保证唯一性;增删查时间复杂度 O (log₂N)。
- 缺点 :效率低于
HashMap;不支持 null Key;非线程安全。 - 适用场景:需要 Key 有序的键值对场景,例如按日期排序的日志映射、按价格排序的商品库存、范围查询的统计数据等。
3.4 Map 实现类对比
| 对比维度 | HashMap | TreeMap | LinkedHashMap | Hashtable(已过时) |
|---|---|---|---|---|
| 底层结构 | 数组 + 链表 + 红黑树 | 红黑树 | 哈希表 + 双向链表 | 哈希表 |
| Key 顺序 | 无序 | 有序(自然 / 自定义) | 有序(插入 / 访问顺序) | 无序 |
| 线程安全 | 否 | 否 | 否 | 是(全表锁) |
| 增删查效率 | O (1)(平均) | O(log₂N) | O (1)(平均,略低于 HashMap) | O (1)(平均,效率低) |
| 支持 null | Key/Value 均可(1 个 null Key) | 均不支持 | Key/Value 均可(1 个 null Key) | 均不支持 |
| 适用场景 | 高效键值对映射 | 有序键值对映射 | 有序键值对(插入 / 访问顺序) | 老旧系统兼容 |
3.5 线程安全的集合方案
上述集合类(ArrayList、HashMap 等)均为非线程安全,多线程环境下需通过以下方案保证线程安全:
3.5.1 Collections 工具类包装
Collections提供了synchronizedXXX()方法,为集合添加同步锁,例如:
java
// 线程安全的List
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
// 线程安全的Map
Map<String, Integer> safeMap = Collections.synchronizedMap(new HashMap<>());
缺点:采用 "全表锁",并发效率低,仅适用于低并发场景。
3.5.2 JUC 包中的线程安全集合
JUC(java.util.concurrent)包提供了专为高并发设计的集合类,核心包括:
- CopyOnWriteArrayList:基于 "写时复制"(COW)机制,读操作无锁,写操作复制数组,适用于读多写少场景。
- ConcurrentHashMap:JDK1.8 采用 "CAS+synchronized" 实现,对链表头节点加锁(细粒度锁),并发效率高,是 HashMap 的线程安全替代。
- CopyOnWriteArraySet:基于 CopyOnWriteArrayList 实现,线程安全的 Set。
示例(ConcurrentHashMap):
java
// 创建线程安全的ConcurrentHashMap
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
// 并发添加键值对
concurrentMap.put("Java", 90);
concurrentMap.put("Python", 85);
// 并发遍历(弱一致性)
for (Map.Entry<String, Integer> entry : concurrentMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
四、算法复杂度:衡量算法效率的标尺
4.1 算法效率的衡量标准
在编程中,我们常说 "这个算法好" 或 "那个算法差",但如何量化这种 "好坏"?算法效率主要通过两个维度衡量:
- 时间复杂度:衡量算法的运行速度,即算法执行所耗费的时间与输入规模的关系。
- 空间复杂度:衡量算法所需的额外空间,即算法运行时临时占用的存储空间与输入规模的关系。
在计算机发展早期,存储空间有限,空间复杂度是重要考量;但随着硬件发展,如今更关注时间复杂度,空间复杂度通常作为次要指标。
4.2 时间复杂度
4.2.1 时间复杂度的定义
时间复杂度是一个数学函数,定量描述了算法的运行时间。它不计算具体的执行时间(因硬件、环境不同而变化),而是关注基本操作的执行次数与输入规模(通常用 N 表示)的增长趋势。
例如,计算 1+2+...+N 的算法:
java
int sum = 0;
for (int i = 1; i <= N; i++) {
sum += i; // 基本操作,执行N次
}
基本操作(sum += i)执行了 N 次,因此时间复杂度与 N 成正比。
4.2.2 大 O 渐进表示法
实际计算时间复杂度时,无需精确统计执行次数,只需关注 "增长趋势最显著的项",这就需要用到大 O 渐进表示法(Big O notation)。
(1)推导大 O 阶的三步法
- 用常数 1 取代所有加法常数:例如执行次数为 "2N+10",常数 10 替换为 1,变为 "2N+1"。
- 保留最高阶项:例如 "2N+1" 的最高阶项是 "2N",保留后为 "2N"。
- 去除最高阶项的系数:例如 "2N" 去除系数 2,最终为 "O (N)"。
(2)示例解析
以如下代码为例,分析其时间复杂度:
java
void func1(int N) {
int count = 0;
// 嵌套循环:执行N*N次
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
count++;
}
}
// 单层循环:执行2*N次
for (int k = 0; k < 2 * N; k++) {
count++;
}
// 循环:执行10次
int M = 10;
while ((M--) > 0) {
count++;
}
System.out.println(count);
}
- 基本操作总次数:F (N) = N² + 2N + 10
- 用大 O 表示法推导:
- 常数 10→1:F (N) = N² + 2N + 1
- 保留最高阶项 N²:F (N) = N²
- 去除系数:O (N²)
- 最终时间复杂度:O (N²)
4.2.3 常见时间复杂度及示例
时间复杂度的增长趋势从低到高依次为:O (1) < O (logN) < O (N) < O (NlogN) < O (N²) < O (2ⁿ) < O (N!),以下是常见复杂度的示例:
(1)O (1):常数阶
基本操作执行次数与输入规模无关,始终为常数次。
java
void func(int N) {
int a = 1;
int b = 2;
int sum = a + b; // 仅执行1次
System.out.println(sum);
}
(2)O (logN):对数阶
基本操作执行次数与输入规模 N 的对数成正比,常见于 "每次排除一半数据" 的场景(如二分查找)。
java
// 二分查找:在有序数组中查找目标值
int binarySearch(int[] array, int target) {
int left = 0;
int right = array.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (array[mid] == target) {
return mid;
} else if (array[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
每次循环排除一半元素,执行次数为 log₂N,时间复杂度为 O (logN)(算法分析中 log 默认以 2 为底)。
(3)O (N):线性阶
基本操作执行次数与输入规模 N 成正比,常见于单层循环。
java
void func(int N) {
int count = 0;
for (int i = 0; i < N; i++) {
count++; // 执行N次
}
System.out.println(count);
}
(4)O (NlogN):线性对数阶
基本操作执行次数为 N×logN,常见于 "外层循环 N 次,内层循环 logN 次" 的场景(如快速排序、归并排序)。
java
// 示例:外层循环N次,内层循环logN次
void func(int N) {
for (int i = 0; i < N; i++) {
// 内层二分查找:O(logN)
int left = 0;
int right = N - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (mid == i) {
break;
} else if (mid < i) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
}
(5)O (N²):平方阶
基本操作执行次数与输入规模 N 的平方成正比,常见于嵌套循环(外层 N 次,内层 N 次)。
java
// 冒泡排序:外层循环N次,内层循环N次
void bubbleSort(int[] array) {
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length - i - 1; j++) {
if (array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
(6)O (2ⁿ):指数阶
基本操作执行次数与 2 的 N 次方成正比,常见于递归场景(如未优化的斐波那契数列),效率极低,应避免。
java
// 斐波那契数列(递归):执行次数为2ⁿ
int fibonacci(int N) {
return N < 2 ? N : fibonacci(N - 1) + fibonacci(N - 2);
}
4.2.4 最好、平均、最坏情况
部分算法的时间复杂度存在三种情况:
- 最好情况:任意输入规模的最小运行次数(下界)。例如在数组中查找元素,最好情况是 1 次找到。
- 平均情况:任意输入规模的期望运行次数。例如在数组中查找元素,平均情况是 N/2 次找到。
- 最坏情况:任意输入规模的最大运行次数(上界)。例如在数组中查找元素,最坏情况是 N 次找到。
在实际分析中,通常关注最坏情况,因为它代表了算法的 "最差性能",是系统设计的重要依据。例如数组查找的时间复杂度按最坏情况定为 O (N)。
4.3 空间复杂度
4.3.1 空间复杂度的定义
空间复杂度是对算法运行过程中临时占用存储空间大小的量度,同样使用大 O 渐进表示法。它不计算具体的字节数,而是统计 "额外变量的个数" 与输入规模的关系。
4.3.2 常见空间复杂度及示例
(1)O (1):常数阶
算法所需额外空间与输入规模无关,始终为常数个变量。
java
// 交换两个变量:仅使用1个临时变量temp
void swap(int a, int b) {
int temp = a; // 额外变量,常数个
a = b;
b = temp;
}
(2)O (N):线性阶
算法所需额外空间与输入规模 N 成正比,常见于动态数组、递归栈帧等场景。
java
// 动态数组:额外空间为N
int[] fibonacci(int N) {
long[] fibArray = new long[N + 1]; // 额外空间为N+1,O(N)
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= N; i++) {
fibArray[i] = fibArray[i - 1] + fibArray[i - 2];
}
return fibArray;
}
(3)O (logN):对数阶
算法所需额外空间与输入规模 N 的对数成正比,常见于递归二分查找(递归栈帧为 logN 层)。
java
// 递归二分查找:递归栈帧为logN层,空间复杂度O(logN)
int binarySearchRecursive(int[] array, int left, int right, int target) {
if (left > right) {
return -1;
}
int mid = (left + right) / 2;
if (array[mid] == target) {
return mid;
} else if (array[mid] < target) {
return binarySearchRecursive(array, mid + 1, right, target);
} else {
return binarySearchRecursive(array, left, mid - 1, target);
}
}
(4)O (N²):平方阶
算法所需额外空间与输入规模 N 的平方成正比,常见于二维数组场景(如矩阵)。
java
// 创建N×N的二维数组:额外空间为N²
int[][] createMatrix(int N) {
int[][] matrix = new int[N][N]; // 额外空间为N×N,O(N²)
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
matrix[i][j] = i * N + j;
}
}
return matrix;
}
4.3.3 空间复杂度与时间复杂度的区别
- 时间复杂度:关注 "执行次数",是 "运行时间" 的衡量。
- 空间复杂度:关注 "额外变量个数",是 "存储空间" 的衡量。
- 权衡关系 :部分算法可通过 "空间换时间" 或 "时间换空间" 优化。例如:
- 缓存机制:用额外空间存储高频访问数据,减少计算时间(空间换时间)。
- 压缩算法:用额外计算时间减少存储空间(时间换空间)。
4.4 复杂度分析实战示例
4.4.1 示例 1:冒泡排序的复杂度分析
java
void bubbleSort(int[] array) {
for (int end = array.length; end > 0; end--) {
boolean sorted = true;
for (int i = 1; i < end; i++) {
if (array[i - 1] > array[i]) {
int temp = array[i - 1];
array[i - 1] = array[i];
array[i] = temp;
sorted = false;
}
}
if (sorted) {
break;
}
}
}
- 时间复杂度 :
- 最好情况:数组已有序,仅执行外层循环 1 次,内层循环 N 次 → O (N)。
- 最坏情况:数组逆序,外层循环 N 次,内层循环 N 次 → O (N²)。
- 实际分析:按最坏情况定为 O (N²)。
- 空间复杂度:仅使用 1 个临时变量 temp 和 1 个布尔变量 sorted → O (1)。
4.4.2 示例 2:递归阶乘的复杂度分析
java
long factorial(int N) {
return N < 2 ? N : factorial(N - 1) * N;
}
- 时间复杂度:递归调用 N 次,每次调用执行 1 次乘法 → O (N)。
- 空间复杂度:递归栈帧为 N 层,每层使用常数个变量 → O (N)。
4.4.3 示例 3:斐波那契数列(递归)的复杂度分析
java
int fibonacci(int N) {
return N < 2 ? N : fibonacci(N - 1) + fibonacci(N - 2);
}
- 时间复杂度:递归调用次数为 2ⁿ(递归树为满二叉树,节点数为 2ⁿ-1) → O (2ⁿ)。
- 空间复杂度:递归栈帧最大深度为 N(最坏情况) → O (N)。

五、总结与实战建议
5.1 集合框架选择指南
根据业务场景选择合适的集合类,是提升程序性能的关键,核心选择逻辑如下:
-
需存储单列数据:
- 有序可重复、频繁查询 → ArrayList。
- 有序可重复、频繁增删 → LinkedList。
- 无序不可重复、高效查询 → HashSet。
- 有序不可重复、需排序 → TreeSet。
- 队列 / 栈场景 → ArrayDeque。
- 优先级任务 → PriorityQueue。
-
需存储键值对数据:
- 高效查询、无需排序 → HashMap。
- 需 Key 有序 → TreeMap。
- 需保留插入 / 访问顺序 → LinkedHashMap。
- 高并发场景 → ConcurrentHashMap。
-
线程安全需求:
- 低并发 → Collections.synchronizedXXX ()。
- 高并发 → CopyOnWriteArrayList、ConcurrentHashMap。
5.2 算法复杂度优化建议
-
时间复杂度优化:
- 避免嵌套循环(O (N²)),尽量用 "空间换时间"(如用哈希表将 O (N²) 优化为 O (N))。
- 递归场景避免重复计算(如斐波那契数列用动态规划优化为 O (N))。
- 大数据量查询优先用二分查找(O (logN))而非线性查找(O (N))。
-
空间复杂度优化:
- 避免不必要的动态数组 / 集合,尽量复用变量。
- 递归深度过大时,用迭代替代递归(减少栈帧空间)。
- 大型数据优先用流式处理,避免一次性加载到内存。
5.3 学习路径建议
- 基础阶段:掌握集合框架的 API 使用,能熟练创建、操作集合。
- 进阶阶段:理解底层数据结构(数组、链表、哈希表、红黑树),能分析集合的性能特点。
- 实战阶段:通过 LeetCode、牛客网刷题,将集合框架与算法复杂度结合,解决实际问题(如 Top-K、哈希冲突处理)。
- 深入阶段:阅读 JDK 源码(如 HashMap、ArrayList),理解底层实现细节(如扩容机制、红黑树旋转)。
通过以上学习,不仅能熟练使用集合框架,更能在实际开发中做出 "性能最优" 的技术选型,同时应对面试中的深度问题,为成为资深 Java 开发者奠定基础。
总结
以上就是今天要讲的内容,本文简单记录了java数据结构,仅作为一份简单的笔记使用,大家根据注释理解,您的点赞关注收藏就是对小编最大的鼓励!