概述

Java 用途

Java 是用于开发应用程序的编程语言程序就是计算机执行某些操作或解决某个问题而编写的一系列有序指令的集合主要用于以下几个核心领域


Android 应用开发Java 是 Android 平台开发的主要语言之一许多我们日常使用的手机 App如 SpotifySignal 等都是使用 Java 构建的
企业级后端开发这是 Java 最重要的应用场景凭借其稳定性安全性和高并发处理能力Java 成为企业级服务端开发的首选大量的银行金融交易系统大型电商网站如 TwitterLinkedIn的后台都> 是由 Java 驱动的
大数据处理Java 是许多大数据技术生态的核心语言例如 Hadoop 和 Spark 等框架都是用 Java 编写的广泛用于处理和分析海量数据
桌面应用程序Java 可以开发跨平台的桌面软件著名的集成开发环境IDE如 IntelliJ IDEA 和 Eclipse 本身就是用 Java 开发的桌面应用
游戏开发Java 也被用于游戏开发最著名的例子就是风靡全球的沙盒游戏我的世界Minecraft
嵌入式系统与物联网 (IoT)得益于其跨平台和稳定性Java 还被应用于智能电视车载系统工业控制设备等嵌入式和物联网领域

Java 历史

🌱 起源OakJava (1991-1994)

Java 的故事始于 1991 年当时 Sun Microsystems 公司启动了一个名为绿色计划Green Project的秘密项目

  • 最初目标 由詹姆斯·高斯林James Gosling等人领导的团队旨在为下一代智能消费电子产品如机顶盒交互式电视开发一种新的编程语言
  • 命名Oak Gosling 将这门新语言命名为Oak橡树灵感来自他办公室窗外的一棵树然而由于硬件厂商接受度低该项目在消费电子市场遭遇失败
  • 关键转折 1994 年互联网Web开始兴起团队敏锐地意识到Oak 语言所具备的安全性网络移动性和跨平台特性完美契合了互联网网络即计算机的愿景
  • 正式更名 由于Oak已被注册为商标团队选择了Java作为新名字灵感来自印度尼西亚的爪哇咖啡

🚀 爆发互联网时代的宠儿 (1995-1999)

1995 年是 Java 历史上最重要的一年

  • 正式发布 1995 年 5 月 23 日Sun Microsystems 在 SunWorld 大会上正式发布 Java 1.0并提出了著名的口号 Write Once, Run Anywhere (WORA)一次编写到处运行
  • 引爆互联网 Java 的跨平台特性解决了当时软件开发的最大痛点Netscape Navigator 浏览器迅速集成 Java 技术使得 Java Applet小程序成为当时网页上实现动态交互的最热门技术

随后的几年是 Java 标准化和壮大的关键期

  • 1996年 JDK 1.0 正式发布标志着 Java 进入主流开发视野
  • 1997年 JDK 1.1 引入了 JDBC数据库连接RMI远程方法调用和内部类等特性完善了语言基础设施
  • 1998年 JDK 1.2 是一个里程碑版本它被重命名为 Java 2并首次将 Java 平台划分为三个版本这一战略奠定了 Java 随后二十年的发展格局
  • J2SE (Java 2 Standard Edition) 面向桌面和工作站的通用开发
  • J2EE (Java 2 Enterprise Edition) 面向服务器端企业级应用包含 EJBServletJSP 等规范
  • J2ME (Java 2 Micro Edition) 面向移动设备如功能手机和嵌入式系统

🏛️ 统治企业级开发的霸主 (2000-2009)

进入 21 世纪随着企业信息化的深入Java 迅速确立了在企业后端开发的统治地位

  • 企业首选 尽管早期的 EJB 因配置繁琐而备受诟病但 Java 凭借其稳定性安全性和庞大的类库成为了银行电信和电商等核心系统的首选语言
  • 语法革命 (Java 5) 2004 年发布的 JDK 5.0 是一次巨大的升级引入了泛型注解枚举自动装箱等特性极大地提升了代码的简洁性和类型安全
  • 开源化 (Java 6) 2006 年Sun 公司宣布将 Java 技术作为免费软件对外发布此举极大地推动了 Java 社区的繁荣
  • 巨头更迭 (2009) 甲骨文公司Oracle宣布收购 Sun 公司Java 的归属权随之转入 Oracle 麾下

🏎️ 进化云原生时代的重生 (2010-至今)

在 Oracle 的推动下Java 进入了一个快速有序的现代化演进阶段

  • 里程碑式的 Java 8 2014 年发布的 Java 8 至今仍是影响最深远的版本之一它引入的 Lambda 表达式和 Stream API 让 Java 拥有了函数式编程能力彻底改变了开发者的编码方式15
  • 模块化与新节奏 (Java 9) 2017 年发布的 Java 9 引入了模块化系统Project Jigsaw并开启了每6个月发布一个新版本每2年发布一个长期支持版本LTS的新节奏25
  • 拥抱云原生 (Java 21) 2023 年发布的 JDK 21 标志着 Java 迈入了协程时代核心特性虚拟线程Virtual Threads让开发者能够轻松创建百万级线程极大地提升了高并发场景下的性能为云原生应用开发注入了强大动力5

Java 特点

⭐⭐ 核心优势


跨平台性 (Write Once, Run Anywhere)
这是 Java 最显著的特征Java 代码在编译后不会生成特定操作系统的机器码而是生成一种中间代码——字节码这些字节码可以在任何安装了 Java 虚拟机 的硬件设备上运行JVM 负责将字节码解释或编译成对应平台的机器码从而实现了一次编写到处运行的愿景极大地提升了软件的可移植性


面向对象 (OOP)
Java 是一门纯粹的面向对象编程语言它完全基于对象的概念来组织代码并支持封装继承多态等核心特性这种编程范式使得代码更易于理解复用和维护特别适合开发大型复杂系统


简单易学
Java 的语法设计去除了 C++ 中复杂且容易出错的特性例如指针多重继承goto 语句同时它采用了自动垃圾回收机制来管理内存减轻了程序员的负担这使得 Java 语言相对简洁对于初学者来说非常友好


⭐⭐ 稳健与安全


健壮性 Java 通过多种机制来保证程序的稳定运行

  • 强类型检查 在编译和运行时都会进行严格的类型检查提前暴露错误
  • 异常处理 提供了完善的 try-catch-finally 机制来捕获和处理运行时错误防止程序意外崩溃
  • 自动内存管理 通过垃圾回收器自动回收不再使用的对象有效避免了内存泄漏和悬空指针等问题

安全性 Java 被设计用于网络和分布式环境其安全性是内置的

  • 沙箱机制 JVM 为代码提供了一个受限的运行环境限制其对本地系统资源如文件网络的访问权限防止恶意代码破坏系统
  • 字节码验证 在加载类文件时会进行严格的校验确保代码不会执行非法操作

⭐⭐ 高性能与并发


高性能
虽然早期 Java 因解释执行而性能受限但现代 JVM 引入了 即时编译 技术JIT 会将频繁执行的热点代码编译成本地机器码使其运行性能接近 C/C++ 等编译型语言


多线程支持
Java 在语言层面原生支持多线程编程开发者可以方便地创建和管理多个线程以实现并发执行任务这对于开发高并发的服务器端应用和响应迅速的图形界面程序至关重要


⭐⭐ 生态与扩展


分布式
Java 从诞生之初就具备网络基因内置了丰富的网络库java.netjava.rmi使得开发分布式应用如 Web 服务远程调用变得非常简单


动态性
Java 能够在运行时动态地加载类获取类信息并调用方法这一特性主要通过反射机制实现它是许多框架如 Spring实现依赖注入面向切面编程等功能的基础极大地增强了程序的灵活性


Java 运行机制

Java 的运行机制是其能够实现一次编写到处运行WORA的核心它并非直接由操作系统执行而是依赖于一个名为 Java 虚拟机 的中间层

简单来说Java 的运行机制可以分为三个核心阶段编译加载执行

⭐⭐ 核心组件JDKJRE 与 JVM

JDK (Java Development Kit) 是 Java 的开发工具包包含了编译器 (javac)调试器等开发工具开发者使用 JDK 将源代码编译成字节码
JRE (Java Runtime Environment) 是 Java 的运行时环境包含了 JVM 和核心类库普通用户只需安装 JRE 即可运行 Java 程序
JVM (Java Virtual Machine) 是 Java 虚拟机是整个运行机制的核心它是一个虚拟的计算机负责加载字节码并将其转换为特定平台的机器码来执行

⭐⭐ 阶段一编译从源码到字节码

这是 Java 程序运行的第一步发生在开发阶段

  1. 输入 开发者编写的 .java 源代码文件
  2. 工具 使用 JDK 中的 javac 编译器
  3. 过程 javac 编译器对源代码进行语法分析语义分析和字节码生成
  4. 输出 生成与平台无关的 .class 字节码文件此时的字节码并不是任何特定硬件的机器码而是一种 JVM 能够理解的中间指令集

⭐⭐ 阶段二加载类加载器的职责

当你在命令行执行 java HelloWorldJVM 启动并进入类加载阶段

  1. 加载 类加载器负责从文件系统或网络中找到 HelloWorld.class 文件并将其二进制字节流读入内存
  2. 链接
    • 验证 确保字节码是合法且安全的符合 JVM 规范防止恶意代码破坏系统
    • 准备 为类的静态变量分配内存并设置其初始默认值0null
    • 解析 将常量池内的符号引用如类名方法名替换为直接引用内存地址
  3. 初始化 执行类构造器 <clinit>() 方法为类的静态变量赋予代码中指定的初始值并执行静态代码块

⭐⭐ 阶段三执行执行引擎的魔法

类加载完成后JVM 的执行引擎开始接管执行 main 方法中的字节码现代 JVM 采用了一种混合模式来平衡启动速度和运行效率


混合执行模式

  1. 解释执行 (Interpreter)
    • 方式 像同声传译一样逐条读取字节码将其翻译成当前平台的机器码并执行
    • 优点 启动速度快无需等待编译
    • 缺点 运行效率较低因为同一段代码每次执行都需要翻译
  2. 即时编译 (JIT - Just-In-Time Compiler)
    • 方式 JVM 会监控程序运行当发现某个方法或代码块被频繁调用称为热点代码JIT 编译器会将其整个编译成本地机器码并缓存起来
    • 优点 后续调用该热点代码时直接执行高效的机器码运行速度极快接近 C/C++
    • 缺点 编译过程会消耗 CPU 和内存资源且首次执行时有延迟

总结 Java 程序启动时解释器优先执行保证快速响应在运行过程中JIT 编译器不断优化热点代码提升系统吞吐量这种解释器与编译器并存的策略是 Java 性能卓越的关键


⭐⭐ 内存管理自动垃圾回收

在整个执行过程中JVM 的运行时数据区负责管理内存其中最核心的机制之一是垃圾回收JVM 会自动追踪对象的引用情况当发现某些对象不再被任何引用指向时垃圾回收器会自动回收其占用的内存极大地减轻了开发者手动管理内存的负担和出错风险

Java 开发的类别


Java SE 是Java平台标准版的简称(Java platform, standard edition)Java SE 以前称为 J2SE适用于标准的应用开发用于开发和部署桌面服务器以及嵌入设备和实时环境中的Java应用程序Java SE包括用于开发Java web服务的类库同时Java SE为Java EE提供了基础
Java EE 是Java平台企业版的简称(Java platform, enterprise edition)以前称为 J2EE适用于企业级的应用服务开发用于简化企业解决方案的开发部署和管理相关的复杂问题的体系结构Java EE建立于Java SE之上具有web服务组件模型以及通信API等特性可以用来实现企业级的面向服务体系结构(SOA)和 Web 2.0 应用程序
Java ME 是Java微版的简称(Java platform, enterprise edition)以前称为 J2ME常用于手机上的开发是一个技术和规范的集合Java ME为在移动设备和嵌入式设备(比如手机PDA电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境Java ME 包括灵活的用户界面健壮的安全模型许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持基于 Java ME 规范的应用程序只需编写一次就可以用于许多设备而且可以利用每个设备的本机功能

基础部分

安装运行

安装配置

JAVA 8 安装详解https://blog.csdn.net/suzengxin/article/details/115401549

运行程序

⭐⭐ 第一步开发程序

1
2
3
4
5
public class Hello {
public static void main(String[] args) {
System.out.println("Hello World");
}
}

⭐⭐ 第二步编译程序

1
javac Hello.java

⭐⭐ 第三步运行程序

1
java Hello

转义字符

转义字符 描述
\t 制表符
\n 换行符
\r 回车
\" 双引号
\' 单引号
\\ 反斜杠

代码注释

单行注释

1
// 单行注释

多行注释

1
2
3
/*
多行注释
*/

文档注释

1
2
3
4
/**
* @author szxck
* @version 1.0
*/

⭐⭐ 生成 Java 文档

1
javadoc -d D:\\javadoc -author -version Hello.java

⭐⭐ 标准标签

标签名 用途说明 适用对象
@author 指明类或接口的作者 接口
@version 指定类或接口的版本信息 接口
@param 描述方法或构造函数的参数名称和含义 方法构造函数
@return 描述方法的返回值 方法
@throws 描述方法可能抛出的异常 方法构造函数
@exception @throws 功能相同用于描述异常 方法构造函数
@see 提供指向其他类方法或外部资源的参见链接 全局(类方法包等)
@since 指明该特性从哪个版本或日期开始引入 全局
@deprecated 标记该 API 已过时不建议使用 全局
@serial 用于序列化字段的文档说明 字段 (Field)
@serialData 描述通过 `writeObject` 或 `writeExternal` 写入的数据 方法 (通常为 writeObject)
@serialField 描述 `ObjectStreamField` 组件 字段

⭐⭐ 内联标签

这些标签用 {} 包裹可以嵌入到注释的任意文本中用于内联显示特殊内容或链接

标签名 用途说明
{@link} 内联插入一个指向其他类或成员的链接类似于 @see 但可嵌入文本
{@linkplain} {@link} 类似但链接文本以纯文本非代码样式显示
{@value} 显示静态字段常量的值
{@code} 将文本显示为代码样式通常用于表示关键字或代码片段并避免被解析为 HTML
{@literal} 显示文本而不进行任何转义显示原样文本不解析为 HTML 或其他标签
{@inheritDoc} 从直接父类或实现的接口中继承文档注释
{@docRoot} 指明生成的文档根目录的路径用于构建相对路径链接

⭐⭐ 不常用标签

标签名 用途说明
@category (非标准某些 Doclet 可能支持) 用于分类
@index (Java 8+) 强制将标识符添加到索引中
@apiNote (Java 8+) 提供关于 API 使用的额外说明非规范性
@implSpec (Java 8+) 提供实现者需要遵守的规范说明
@implNote (Java 8+) 提供关于实现的非规范性说明

数据类型

在 Java 中数据类型变量常量是编程的三大基石它们共同决定了程序如何存储和处理数据简单来说数据类型是容器的规格变量是可变的容器而常量是不可变的容器


数据类型用于定义变量或常量可以存储的数据种类以及占用内存的大小Java 是强类型语言每一个变量都必须先声明类型才能使用
变量变量是程序运行过程中值可以发生改变的数据存储单元你可以把它想象成一个贴了标签的盒子盒子的大小由数据类型决定里面的内容可以根据需要更换
常量常量是程序运行过程中值不能发生改变的数据 使用 final 关键字修饰的变量一旦赋值就不能再修改

类型描述

类型 关键字 字节/位数 取值范围 默认值 描述
整数型 byte 1字节 / 8位 -128 到 127 0 最小的整数类型常用于节省内存的场景如处理二进制数据
short 2字节 / 16位 -32768 到 32767 0 较少使用通常用于特定的内存优化场景
int 4字节 / 32位 -2³¹ 到 2³¹-1 0 最常用的整数类型也是整数字面量的默认类型
long 8字节 / 64位 -2⁶³ 到 2⁶³-1 0L 用于存储非常大的数值如时间戳文件大小定义时需在数值后加 L
浮点型 float 4字节 / 32位 约 ±3.4028235E+38 0.0F 单精度浮点数定义时需在数值后加 F
double 8字节 / 64位 约 ±1.7976931348623157E+308 0.0D 双精度浮点数精度更高是浮点数字面量的默认类型
字符型 char 2字节 (16位) 0 到 65535 (\u0000 到 \uffff) \u0000 使用单引号 '' 表示例如'中''国''A'
布尔型 boolean 1 位 | 1 字节 true (真) 或 false (假) false 常用于条件判断和循环控制

类型转换

⭐⭐ 自动类型转换隐式转换

这是 Java 编译器自动完成的转换也叫拓宽转换Widening Conversion当你把一个小杯子里的水倒进一个大杯子不会溢出因此是安全的

转换规则 容量小的数据类型可以直接赋值给容量大的数据类型

byte → short → int → long → float → double

char → int → long → float → double

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 赋值时的自动转换
int a = 100;
long b = a; // int -> long自动转换
double c = a; // int -> double自动转换

// 2. char的特殊性
char ch = 'A'; // 'A'的Unicode值是65
int ascii = ch; // char -> int自动转换结果为65

// 3. 运算时的自动提升
byte x = 10;
short y = 20;
// byte + short -> 计算前先全部转为 int所以结果必须用 int 或更大的类型接收
int result = x + y;

float f = 1.5f;
double d = 2.5;
// float + double -> 提升为 double
double sum = f + d;

⭐⭐ 强制类型转换显式转换

这是程序员明确指示编译器进行的转换也叫窄化转换Narrowing Conversion当你把一个大杯子里的水倒进一个小杯子可能会溢出或洒掉数据丢失因此需要你手动负责

精度丢失 浮点数转整数时直接截断小数部分不是四舍五入

数据溢出 数值超过了目标类型的取值范围会发生截断溢出

1
2
3
4
5
6

double pi = 3.1415926;
int intPi = (int) pi; // 强制转换结果为 3直接舍弃小数

long bigNum = 3000000000L;
int num = (int) bigNum; // 强制转换由于long范围大于int可能导致溢出结果可能为负数

二进制

计算机中的数据存储 学习笔记

各个进制之间相互转换 学习笔记

原码反码和补码 学习笔记

运算符

Java运算符是用于对变量和值进行操作的符号

算术运算符

算术运算符用于执行基本的数学运算

运算符 名称 示例 描述
+ 加法 a + b 相加两个操作数
- 减法 a - b 从第一个操作数中减去第二个操作数
* 乘法 a * b 相乘两个操作数
/ 除法 a / b 分子除以分母
% 取模 ++a a++ 返回除法的余数
++ 自增 ++a a++ 操作数的值增加1
-- 自减 --a a-- 操作数的值减少1

⭐⭐ 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ArithmeticOperators {
public static void main(String[] args) {
int a = 10, b = 3;
System.out.println("a + b = " + (a + b)); // 13
System.out.println("a - b = " + (a - b)); // 7
System.out.println("a * b = " + (a * b)); // 30
System.out.println("a / b = " + (a / b)); // 3 (整数除法)
System.out.println("a % b = " + (a % b)); // 1

int c = 5;
System.out.println("c++ = " + (c++)); // 5 (先使用后自增)
System.out.println("++c = " + (++c)); // 7 (先自增后使用)
}
}

关系运算符

关系运算符用于比较两个值返回布尔值true或false

运算符 名称 示例 描述
== 等于 a == b 检查两个操作数的值是否相等
!= 不等于 a != b 检查两个操作数的值是否不相等
> 大于 a > b 检查左操作数的值是否大于右操作数的值
< 小于 a < b 检查左操作数的值是否小于右操作数的值
>= 大于等于 a >= b 检查左操作数的值是否大于或等于右操作数的值
<= 小于等于 a <= b 检查左操作数的值是否小于或等于右操作数的值

⭐⭐ 示例代码

1
2
3
4
5
6
7
8
9
10
11
public class RelationalOperators {
public static void main(String[] args) {
int a = 10, b = 20;
System.out.println("a == b: " + (a == b)); // false
System.out.println("a != b: " + (a != b)); // true
System.out.println("a > b: " + (a > b)); // false
System.out.println("a < b: " + (a < b)); // true
System.out.println("a >= b: " + (a >= b)); // false
System.out.println("a <= b: " + (a <= b)); // true
}
}

逻辑运算符

逻辑运算符用于组合多个布尔表达式

运算符 名称 示例 描述
&& 短路与 a && b 如果两个操作数都为真则返回结果为真
|| 短路或 a || b 如果两个操作数有一个为真则返回结果为真
& 逻辑与 a & b 如果两个操作数都为真则返回结果为真
| 逻辑或 a | b 如果两个操作数有一个为真则返回结果为真
^ 逻辑异或 a ^ b 两个操作数一个为真一个为假则返回结果为真
! 取反 !a 如果操作数为真则返回结果为假反转逻辑状态

⭐⭐ 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// &&如果第一个条件为false不会检查第二个条件
// & 如果第一个条件为false还会继续检查第二个条件
// ||如果第一个条件为true不会检查第二个条件
// | 如果第一个条件为true还会继续检查第二个条件
public class LogicalOperators {
public static void main(String[] args) {
boolean x = true, y = false;

System.out.println("x && y: " + (x && y)); // false
System.out.println("x || y: " + (x || y)); // true
System.out.println("!x: " + (!x)); // false

// 短路示例
int a = 5, b = 10;
if (a > 10 && b++ > 5) {
System.out.println("条件成立");
}
System.out.println("b = " + b); // b仍然是10因为短路
}
}

位运算符

位运算符对整数类型的位进行操作

运算符 名称 示例 描述
& 按位与 a & b 只有当两个对应的二进制位都为 1 时结果的该位才为 1否则为 0
| 按位或 a | b 只要有一个对应的二进制位为 1结果位就为 1否则为 0
^ 按位异或 a ^ b 当两个对应的二进制位不相同时结果位为 1否则相同时为 0
~ 按位取反 ~a 当原二进制位为 0 时结果位为 1当原位为 1 时结果位为 0
<< 左移 a << 1 将a的二进制位向左移动1位右边空出的位补入 0
左移几位相当于乘以 2 的几次方
>> 右移 a >> 1 将a的二进制位向右移动1位左边空出的位补入符号位
右移几位相当于除以 2 的几次方
>>> 无符号右移 a >>> 1 将a的二进制位向右移动1位左边空出的位补入 0
处理的是二进制位的移动而不是数学计算

⭐⭐ 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 在 Java 中所有整数在计算机底层都以 二进制补码 的形式存储
// 对于正数来说其原码反码和补码是相同的
// 对于负数来说原码反码和补码计算遵循一套固定的规则
// 反码 = 原码的符号位不变其他位按位取反
// 补码 = 反码 + 1
public class BitwiseOperators {
public static void main(String[] args) {
int a = 6; // 二进制: 0000 0000 0000 0000 0000 0000 0000 0110
int b = 3; // 二进制: 0000 0000 0000 0000 0000 0000 0000 0011
System.out.println("a & b = " + (a & b)); // 2 (0010)
System.out.println("a | b = " + (a | b)); // 7 (0111)
System.out.println("a ^ b = " + (a ^ b)); // 5 (0101)
// a = 6 因为是正数所以 6 补码跟原码一致
// 补码 0000 0000 0000 0000 0000 0000 0000 0110
// 取反 1111 1111 1111 1111 1111 1111 1111 1001 (按位取反符号位是1所以是负数)
// 转反码 1111 1111 1111 1111 1111 1111 1111 1000 (负数反码 = 补码 - 1)
// 转原码 1000 0000 0000 0000 0000 0000 0000 0111 (符号位不变数值位取反)
System.out.println("~a = " + (~a)); // -7 (转十进制)
System.out.println("a << 1 = " + (a << 1)); // 12 (1100) > 6 * 2的1次方
System.out.println("a >> 1 = " + (a >> 1)); // 3 (0011) > 6 / 2的1次方
}
}

赋值运算符

赋值运算符用于给变量赋值

运算符 示例 等价于
= a = 5 简单赋值
+= a += 5 a = a + 5
-= a -= 5 a = a - 5
*= a *= 5 a = a * 5
/= a /= 5 a = a / 5
%= a %= 5 a = a % 5
&= a &= 5 a = a & 5
|= a |= 5 a = a | 5
^= a ^= 5 a = a ^ 5
<<= a <<= 5 a = a << 5
>>= a >>= 5 a = a >> 5
>>>= a >>>= 5 a = a >>> 5

⭐⭐ 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AssignmentOperators {
public static void main(String[] args) {
int a = 10;

a += 5; // a = a + 5
System.out.println("a += 5: " + a); // 15

a -= 3; // a = a - 3
System.out.println("a -= 3: " + a); // 12

a *= 2; // a = a * 2
System.out.println("a *= 2: " + a); // 24
}
}

三元运算符

条件运算符是Java中唯一的三元运算符

语法 条件表达式 ? 表达式1 : 表达式2条件表达式为真返回表达式1为假返回表达式2

⭐⭐ 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TernaryOperator {
public static void main(String[] args) {
int a = 10, b = 20;

// 使用三元运算符找出最大值
int max = (a > b) ? a : b;
System.out.println("最大值: " + max); // 20

// 判断奇偶数
String result = (a % 2 == 0) ? "偶数" : "奇数";
System.out.println(a + "是" + result); // 10是偶数
}
}

特殊运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 字符串连接运算符 (+)
// 当 `+` 运算符用于字符串时它会将其他类型的操作数转换为字符串并连接
String str = "Hello" + " " + "World"; // "Hello World"
String result = "The answer is " + 42; // "The answer is 42"


// instanceof 运算符
// 用于检查对象是否是指定类型的实例
String str = "Hello";
System.out.println("str instanceof String: " + (str instanceof String)); // true
System.out.println("str instanceof Integer: " + (str instanceof Integer)); // false


// 方法引用运算符 (::)
// Java 8 引入的方法引用用于直接引用已有方法或构造器
System.out::println; // 调用该方法 System.out.println();

运算符优先级

当表达式包含多个运算符时Java按照优先级顺序执行

优先级 运算符 运行顺序
1 `()` `[]` `.` 从左到右
2 `++` `--` `!` `~` `+` `-` `(type)` `new` 从右到左
3 `*` `/` `%` 从左到右
4 `+` `-` 从左到右
5 `<<` `>>` `>>>` 从左到右
6 `<` `<=` `>` `>=` `instanceof` 从左到右
7 `==` `!=` 从左到右
8 `&` 从左到右
9 `^` 从左到右
10 `|` 从左到右
11 `&&` 从左到右
12 `||` 从左到右
13 `? :` 从右到左
14 赋值运算符 从右到左

⭐⭐ 示例代码

1
2
int result = 10 + 5 * 2; // 先乘法后加法结果为20
int result2 = (10 + 5) * 2; // 先加法后乘法结果为30

控制结构

Java 的控制结构决定了代码的执行顺序和逻辑走向简单来说它让程序不再是一条路走到黑的顺序执行而是具备了判断分支和重复循环的能力

顺序结构

这是最简单的结构程序默认从上到下逐行执行中间没有任何判断和跳转

1
2
3
4
5
6
// 特点代码按照书写顺序依次运行
// 注意在定义变量时必须遵循前向引用原则即变量需要先声明再使用
public static void main(String[] args) {
int a = 10;
int b = a + 2;// a 变量需要先声明再使用
}

分支控制

⭐⭐ 单分支结构

    // 基本语法
    if (条件表达式) {
        // 代码块当条件表达式为 true 时执行
    }

    1.程序首先计算 条件表达式 的值
    2.如果结果为 true则执行大括号 {} 内的代码块
    3.如果结果为 false则跳过整个 if 结构执行紧跟在 if 后面的下一行代码
    4.如果代码块中只有一行语句大括号 {} 可以省略但强烈不建议省略为了代码可读性和避免错误
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
double balance = 500.0; // 账户余额
double withdraw = 800.0; // 取款金额
// 判断余额是否充足余额不足打印信息
if (withdraw > balance) {
System.out.println("余额不足取款失败");
System.out.println("当前余额: " + balance);
}
// 如果余额充足什么也不做直接执行后续业务
}

⭐⭐ 双分支结构

    // 基本语法
    if (条件表达式) {
        // 代码块1当条件为 true 时执行
    } else {
        // 代码块2当条件为 false 时执行
    }

    1.程序首先计算 条件表达式 的值
    2.如果结果为 true执行 代码块1
    3.如果结果为 false执行 代码块2
    4.执行完毕后执行紧跟在 if 后面的下一行代码
1
2
3
4
5
6
7
8
public static void main(String[] args) {
int number = 7;
if (number % 2 == 0) {
System.out.println(number + " 是偶数");
} else {
System.out.println(number + " 是奇数");
}
}

⭐⭐ 多分支结构

    // 基本语法
    if (条件表达式1) {
        // 代码块1
    } else if (条件表达式2) {
        // 代码块2
    } else {
        // 默认代码块以上条件都不满足时执行
    }

    1.自上而下依次判断条件表达式
    2.一旦遇到某个 条件表达式 为 true就执行对应的代码块执行完毕后立即跳出整个结构不再判断后续的 else if
    3.如果所有的 if 和 else if 条件都不成立且存在 else 分支则执行 else 中的代码块
    4.else 分支如果没有要处理的代码块可以省略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
int score = 85;
String grade;
if (score >= 90) {
grade = "A (优秀)";
} else if (score >= 80) {
grade = "B (良好)";
} else if (score >= 70) {
grade = "C (中等)";
} else if (score >= 60) {
grade = "D (及格)";
} else {
grade = "E (不及格)";
}
System.out.println("分数: " + score + ", 等级: " + grade);
}

⭐⭐ 嵌套分支结构

    // 基本语法
    if (外层条件表达式) {
        // 外层代码块
        if (内层条件表达式) {
            // 内层代码块 A
        } else {
            // 内层代码块 B
        }
    } else {
        // 外层代码块
    }

    1.程序首先判断外层条件只有在外层条件成立的情况下才会去执行内部的内层条件判断
    2.内层的 if-else 完全属于外层 if 或 else 的代码块范围
    3.逻辑关系通常用于表达并且的关系例如如果是男性 并且 年龄大于 18 -> 外层判断性别内层判断年龄
    4.嵌套分支结构其实就是将上述的单分支双分支或多分支结构互相包裹起来形成里外两层或多层的判断逻辑
    5.理论上 Java 支持无限层嵌套但在实际开发中为了代码的可读性和维护性建议嵌套层次不要超过 3 层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) {
String inputPassword = "888888";
String realPassword = "888888";
double balance = 1000.0;
double withdraw = 1500.0;

if (inputPassword.equals(realPassword)) {
System.out.println("密码正确正在验证金额...");

// 嵌套开始只有密码正确才需要验证金额
if (withdraw <= balance) {
System.out.println("取款成功请取走现金");
} else {
System.out.println("余额不足取款失败");
}
// 嵌套结束

} else {
System.out.println("密码错误卡已锁定请联系银行");
}
}

⭐⭐ 多值匹配结构

    // 基本语法
    switch (表达式) {
        case 值1:
            // 执行代码块1
            break;
        case 值2:
            // 执行代码块2
            break;
        case 值3:
        case 值4: // 多个case共用代码
            // 执行代码块3或4
            break;
        default:
            // 当所有case都不匹配时执行
    }

    1.表达式求值switch 后面括号中的表达式只会被计算一次
    2.顺序匹配从上到下依次将表达式的值与 case 后面的值进行比较一旦找到匹配的 case程序会从该处开始执行代码
    3.case 的值必须是字面量或 final 常量不能是变量或表达式
    4.break 语句用于终止 switch 结构跳出整个代码块
    5.穿透现象如果省略 break程序不会停止而是会继续执行下一个 case 或 default 的代码直到遇到 break 或 switch 结束
    6.除非刻意利用穿透否则每个 case 后都应该写 break否则会导致逻辑错误
    7.default 分支当所有 case 都不匹配时执行它是可选的位置可以放在 switch 块的任何地方但为了代码可读性强烈建议放在末尾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
int day = 3;
switch (day) {
case 1:
System.out.println("星期一");
break;
case 2:
System.out.println("星期二");
break;
case 3:
System.out.println("星期三");
break;
case 4:
System.out.println("星期四");
break;
default:
System.out.println("周末或输入错误");
}
}

循环控制

⭐⭐ for 循环

    // 基本语法
    for (初始化语句; 循环条件; 迭代语句) {
        // 循环体重复执行的代码
    }

    1.初始化语句最先执行且只执行一次通常用于定义循环变量如 int i = 0
    2.循环条件必须是一个布尔表达式在每次循环开始前进行判断
    3.如果结果为 true则执行循环体
    4.如果结果为 false则终止循环跳出 for 结构
    5.循环体条件为 true 时执行的具体代码
    6.迭代语句在循环体执行完毕后执行通常用于更新循环变量如 i++
    7.流程顺序初始化 -> 判断条件 -> 执行循环体 -> 执行迭代语句 -> 再次判断条件……直到条件为 false
1
2
3
4
5
6
7
8
public static void main(String[] args) {
int sum = 0;
// i 从 1 开始只要 i <= 100 就继续循环每次循环后 i 自增 1
for (int i = 1; i <= 100; i++) {
sum += i; // 累加
}
System.out.println("1 到 100 的和是: " + sum);
}

⭐⭐ while 循环

    // 基本语法
    while (循环条件) {
        // 循环体
        // 迭代语句通常放在循环体末尾
    }

    1.如果条件为 true则执行循环体中的代码
    2.如果条件为 false则直接跳过整个 while 循环执行循环后面的代码
    3.注意由于是先判断如果一开始条件就是 false循环体一次都不会执行
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
int sum = 0;
int i = 1;
// 只要 i <= 100 就继续循环
while (i <= 100) {
sum += i; // 累加
i++; //i 自增 1
}
System.out.println("1 到 100 的和是: " + sum);
}

⭐⭐ do-while 循环

    // 基本语法
    do {
        // 循环体
        // 迭代语句
    } while (循环条件); // 注意这里的分号不能丢

    1.先执行无论条件是否成立循环体至少会执行一次
    2.后判断执行完循环体后再判断 循环条件
    3.如果条件为 true则继续执行循环体
    4.如果条件为 false则终止循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int secret = 8;
int guess;

do {
System.out.print("请猜一个数字 (1-10): ");
guess = scanner.nextInt();
if (guess > secret) {
System.out.println("太大了再试一次");
} else if (guess < secret) {
System.out.println("太小了再试一次");
}
// 如果猜对了guess == secret循环条件为 false循环结束
} while (guess != secret);

System.out.println("恭喜你猜对了");
scanner.close();
}

⭐⭐ 循环嵌套

    // 基本语法
    for (初始化语句1; 循环条件1; 迭代语句1) {
        // 外层循环体
        for (初始化语句2; 循环条件2; 迭代语句2) {
            // 内层循环体核心逻辑通常在这里
        }
    }

    1.循环嵌套可以是任意循环forwhiledo-while的互相组合
    2.最常见的是 for 循环嵌套 for 循环
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
System.out.println("九九乘法表");
// 外层循环控制行数 (1-9行)
for (int i = 1; i <= 9; i++) {
// 内层循环控制列数 (每行打印的列数等于当前行数)
for (int j = 1; j <= i; j++) {
System.out.print(j + "*" + i + "=" + (i*j) + "\t");
}
System.out.println(); // 每行结束后换行
}
}

流程控制

⭐⭐ break 终止循环

1
2
3
4
5
6
7
8
9
10
11
12
13
// 一旦执行 break程序会立刻跳出当前所在的循环
// 循环中 break 后面的代码不会执行循环的迭代如 i++也会停止
public static void main(String[] args) {
// 场景寻找 1 到 10 中的第一个偶数 4
for (int i = 1; i <= 10; i++) {
if (i == 4) {
System.out.println("找到数字 4停止寻找");
break; // 循环直接结束i 不会再变成 5, 6...
}
System.out.println("正在检查数字: " + i);
}
System.out.println("循环结束后的后续代码");
}

⭐⭐ continue 跳过本次循环

1
2
3
4
5
6
7
8
9
10
11
12
// 一旦执行 continue程序会立刻跳过当前这一次循环中剩余未执行的代码
// 它不会终止整个循环而是直接去执行循环的迭代部分如 i++和条件判断准备开始下一次循环
public static void main(String[] args) {
// 场景打印 1 到 5但跳过数字 3
for (int i = 1; i <= 5; i++) {
if (i == 3) {
System.out.println("跳过数字 3");
continue; // 跳过本次剩余代码即下面的打印直接进入下一次 i=4 的循环
}
System.out.println("当前数字: " + i);
}
}

⭐⭐ return 结束方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 无论 return 写在方法的哪个角落哪怕在最深层的循环里一旦执行整个方法立刻停止
// 方法中 return 后面的所有代码都不会再执行
public class ReturnDemo {

// 有返回值的方法
public static int findNumber() {
for (int i = 1; i <= 10; i++) {
if (i == 5) {
System.out.println("找到目标数字 5直接结束方法并返回结果");
return i; // 直接跳出整个方法循环和方法后续代码都不执行了
}
System.out.println("检查数字: " + i);
}
// 如果循环正常结束都没找到返回 -1
return -1;
}

public static void main(String[] args) {
int result = findNumber();
System.out.println("方法返回的结果是: " + result);
}
}

面向对象

Java 是一门纯面向对象的编程语言这意味着在 Java 中万物皆对象面向对象编程Object-Oriented Programming简称 OOP是一种编程思想它将现实世界中的事物抽象为程序中的对象通过对象之间的交互来完成任务

数组

数组是 Java 中一种特殊的对象它是存储相同类型数据的固定长度的容器通过索引进行访问

核心概念


类型统一数组只能存储相同数据类型的元素比如 int[] 只能存整数String[] 只能存字符串
长度固定数组一旦创建其长度能存放多少个元素不可改变
索引访问数组中的元素通过索引下标访问索引从 0 开始最大索引是 数组长度 - 1
越界风险如果访问 arr[-1] 或 arr[长度]程序会抛出 ArrayIndexOutOfBoundsException
内存连续数组在内存中占据一块连续的空间这使得通过索引查找元素的速度极快随机访问

定义与初始化

方式 语法示例 说明 适用场景
静态初始化 int[] arr = {1, 2, 3}; 声明时直接赋值长度由系统自动推断 元素值已知固定的场景如配置项
动态初始化 int[] arr = new int[5]; 先指定长度由系统赋予默认值0/false/null 长度已知但元素值需要后续计算或用户输入
完整写法 int[] arr = new int[]{1, 2, 3}; 静态初始化的完整形式new 关键字不能省 声明和赋值分开写或者作为方法参数传递时

基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void main(String[] args) {
// 1. 静态初始化已知元素
String[] names = {"张三", "李四", "王五"};

// 2. 动态初始化已知长度5个默认值为 0
int[] scores = new int[5];
scores[0] = 95; // 手动赋值
scores[1] = 87;

// 3. 声明和初始化分开
double[] prices;
prices = new double[]{19.9, 29.5, 8.8}; // 必须用 new 关键字

int[] nums = {10, 20, 30};
System.out.println(nums[0]); // 输出 10 (读取)

nums[1] = 99; // 修改将第2个元素改为 99
System.out.println(nums[1]); // 输出 99

// 获取长度
int len = nums.length; // 返回 3

// 遍历数组
for (int i = 0; i < nums.length; i++) {
System.out.println("索引 " + i + " 的值是: " + nums[i]);
}
}

二维数组

如果一维数组是一行格子二维数组就是一个表格行和列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 方式1静态初始化规则矩阵
int[][] matrix1 = {
{1, 2, 3},
{4, 5, 6}
};

// 方式2动态初始化不规则矩阵/锯齿数组
int[][] matrix2 = new int[2][]; // 2行
matrix2[0] = new int[3]; // 第0行有3列
matrix2[1] = new int[5]; // 第1行有5列

// 遍历二维数组通常使用嵌套循环
// 打印二维数组
for (int i = 0; i < matrix1.length; i++) { // 外层遍历行
for (int j = 0; j < matrix1[i].length; j++) { // 内层遍历列
System.out.print(matrix1[i][j] + " ");
}
System.out.println(); // 换行
}

类与对象

类是一个抽象的概念它描述了一类事物的共同特征和行为对象是类的具体实例是内存中真实存在的实体

类是对象的模板蓝图对象是类的实例具体产品

⭐⭐ 定义一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义一个名为 Phone手机的类
public class Phone {
// --- 属性成员变量 ---
String brand; // 品牌
String model; // 型号
double price; // 价格

// --- 行为成员方法 ---
// 打电话方法
public void call(String name) {
System.out.println("正在给" + name + "打电话...");
}
// 发短信方法
public void sendMessage(String content) {
System.out.println("发送短信" + content);
}
}

⭐⭐ 创建并使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestPhone {
public static void main(String[] args) {
// 1. 创建对象 (实例化)
// new Phone() 在堆内存中开辟空间构造对象
// phone1 是栈中的引用变量指向堆中的对象
Phone phone1 = new Phone();

// 2. 给对象的属性赋值
phone1.brand = "苹果";
phone1.model = "iPhone 15";
phone1.price = 5999.0;

// 3. 调用对象的方法
System.out.println(phone1.brand + "的价格是" + phone1.price);
phone1.call("张三");
phone1.sendMessage("晚上吃饭");

// 4. 可以创建无数个对象
Phone phone2 = new Phone();
phone2.brand = "华为";
phone2.model = "Mate 60";
phone2.call("李四");
}
}

属性

属性用于描述对象的状态或特征它定义在类中方法之外

⭐⭐ 属性的构成

一个完整的属性声明通常包含以下几部分

  • 访问修饰符控制谁可以访问private, public, protected
  • 非访问修饰符改变属性的特性static, final, transient
  • 数据类型可以是基本类型int, boolean或引用类型String, List
  • 属性名变量名
  • 初始值可选的默认值

⭐⭐ 属性的定义

1
2
3
4
5
6
7
8
9
10
public class Car {
// 1. 实例属性 (每个车对象都有自己的颜色)
private String color = "红色";

// 2. 类属性 (所有车共享一个轮子数量static修饰)
public static int wheelCount = 4;

// 3. 常量 (final修饰不可变)
public final String BRAND = "丰田";
}

方法

方法用于描述对象的行为或功能它是一段封装好的可以被重复调用的代码块

⭐⭐ 方法的组成

1
2
3
4
5
6
7
8
9
10
[访问修饰符] [非访问修饰符] 返回值类型 方法名([参数列表]) {
// 方法体
[return 返回值;]
}

返回值类型方法执行完后返回的结果类型如果不需要返回值写 void
方法名动词开头小驼峰命名法如 calculateSum
参数列表方法的输入可以没有参数也可以有多个参数用逗号分隔
方法体具体的逻辑代码
return 语句结束方法执行并返回结果

⭐⭐ 实例方法

定义没有 static 修饰的方法
特点必须先创建对象new然后通过对象名调用
用途通常用于操作对象的状态属性

1
2
3
4
5
6
7
8
public class MethodConcepts {
public void instanceMethod() {
System.out.println("这是实例方法属于具体的对象");
}
}

MethodConcepts mc = new MethodConcepts();
mc.instanceMethod(); // 通过对象调用

⭐⭐ 静态方法

定义用 static 修饰的方法
特点属于类本身不属于任何对象可以直接通过类名调用如 Math.sqrt()
限制静态方法中不能直接访问实例变量或实例方法只能访问静态成员

1
2
3
4
5
6
7
8
public class MethodConcepts {
// 使用 static 修饰属于类直接用 类名.方法名 调用
public static void staticMethod() {
System.out.println("这是静态方法属于类本身");
}
}

MethodConcepts.staticMethod(); // 直接通过类名调用

⭐⭐ 传参机制

Java 中只有值传递没有引用传递

基本类型传递的是值的副本在方法内修改参数不会影响原始变量
引用类型传递的是对象地址的副本在方法内可以通过这个地址修改对象的内容属性但无法改变外部引用变量的指向
对于 String 等不可变类看似传值其实是对象本身不允许修改不是传递方式变了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MethodConcepts {
// 基本类型传递的是值的副本
public void changePrimitive(int x) {
x = 100; // 只修改副本
}

// 引用类型传递的是地址的副本
public void changeReference(int[] array) {
array[0] = 99; // 通过副本地址修改了堆里的原数据
}
}

// 初始化对象
MethodConcepts mc = new MethodConcepts();

// 基本类型传参
int num = 10;
mc.changePrimitive(num);
System.out.println("基本类型传参后: " + num); // 输出 10原值没变

// 引用类型传参
int[] arr = {1, 2, 3};
mc.changeReference(arr);
System.out.println("引用类型传参后 arr[0]: " + arr[0]); // 输出 99内容变了

⭐⭐ 可变参数

可变参数VarargsVariable Arguments 的缩写是一种非常实用的语法特性它允许你编写能够接收不定数量参数的方法极大地提高了代码的灵活性和简洁性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class VarargsExample {

// 定义一个可变参数方法计算任意个整数的和
public static int sum(int... numbers) {
// 在方法内部numbers 被当作 int[] 数组处理
int total = 0;
for (int num : numbers) {
total += num;
}
return total;
}
}
VarargsExample ve = new VarargsExample();
System.out.println(sum(1, 2)); // 传入2个参数
System.out.println(sum(1, 2, 3, 4, 5)); // 传入5个参数
System.out.println(sum()); // 传入0个参数空数组

⭐⭐ 方法的重载

定义在同一个类中允许存在多个同名方法但参数列表必须不同参数个数类型或顺序不同
用途实现同一种行为多种输入方式例如print(int i) 和 print(String s)
注意仅返回值不同是不能构成重载的编译器会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MethodConcepts {
// 同一个类中方法名相同参数列表不同
public int add(int a, int b) {
return a + b;
}

// 参数个数不同
public int add(int a, int b, int c) {
return a + b + c;
}

// 参数类型不同
public double add(double a, double b) {
return a + b;
}
}

MethodConcepts mc = new MethodConcepts();
System.out.println("两个整数相加: " + mc.add(1, 2)); // 调用 add(int, int)
System.out.println("三个整数相加: " + mc.add(1, 2, 3)); // 调用 add(int, int, int)
System.out.println("两个小数相加: " + mc.add(1.5, 2.5)); // 调用 add(double, double)

构造器

构造器也称为构造方法或构造函数是一种特殊的方法它的主要任务是在创建对象时初始化对象

⭐⭐ 构造器的特征

构造器虽然看起来像方法但它和普通方法有本质区别

  • 名字必须与类名完全相同包括大小写
  • 没有返回值类型连 void 都不能写如果写了返回值类型它就变成了普通方法
  • 不能被 staticfinalabstractsynchronizednative 修饰构造器只能由 new 关键字调用不能被继承也不能被静态修饰
  • 自动调用当你使用 new 创建对象时构造器会自动执行无需你手动调用

⭐⭐ 构造器的分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 第一种默认构造器
// 在一个类中没有显式定义任何构造器Java 编译器会自动为你生成一个无参的构造器


// 第二种无参构造器显式定义的一个不带参数的构造器
public class Person {
public Person() {
System.out.println("无参构造器被调用了");
}
}

// 第三种有参构造器带参数的构造器用于在创建对象时直接给对象的属性赋值
public class Person {
private String name;
private int age;

// 有参构造器
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

// 注一旦写了有参构造Java 编译器默认的无参构造就没了
// 如果你既需要有参构造又需要无参构造必须手动把无参构造定义出来

⭐⭐ this 关键字与构造器

在构造器中this 代表当前正在创建的对象它主要用于解决参数名与属性名冲突的问题

1
2
3
4
public Person(String name, int age) {
this.name = name; // this.name 指代成员变量name 指代参数
this.age = age;
}

⭐⭐ 构造器重载

一个类可以有多个构造器只要它们的参数列表不同参数个数类型或顺序不同这和方法重载的原理一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Person {
private String name;
private int age;

// 1. 无参构造
public Person() {
this.name = "未知";
this.age = 0;
}

// 2. 只传名字
public Person(String name) {
this.name = name;
this.age = 0;
}

// 3. 全参构造
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

⭐⭐ 构造器链

在一个构造器中你可以通过 this() 调用本类的另一个构造器或者通过 super() 调用父类的构造器

  • this()super() 必须是构造器中的第一行语句两者不能同时出现在同一个构造器中因为都必须是第一行
  • 如果你在子类构造器中没有显式写 super()Java 会自动帮你插入 super()即调用父类的无参构造器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Person {
private String name;
private int age;
private String gender;

// 构造器
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}

// 简化版构造器通过 this() 调用构造器
public Person(String name, int age) {
this(name, age, "未知"); // 调用上面的构造器性别默认"未知"
}
}

核心特性

封装继承多态是 Java 面向对象编程OOP的三大核心特性它们就像盖房子的三大法则封装负责把砖石砌成独立的房间安全继承负责让子楼房复用地基的结构复用多态则让同一个开关能控制不同房间的灯灵活

⭐⭐ 封装 (Encapsulation)隐藏细节保证安全

对外只暴露必要的接口将对象的内部实现细节数据和逻辑隐藏起来防止外部随意篡改

  • 安全 防止数据被非法设置
  • 易用 调用者无需关心内部怎么实现只要会用接口就行
  • 解耦 内部实现逻辑变了只要接口不变外部调用代码就不需要改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Person {
// 1. 私有化属性外部无法直接访问
private int age;

// 2. 对外部提供给属性赋值的方法在其中加入校验逻辑
public void setAge(int age) {
if (age < 0 || age > 150) {
System.out.println("年龄不合法已设为默认值18");
this.age = 18;
} else {
this.age = age; // 合法则赋值
}
}

// 3. 对外部提供访问属性的方法
public int getAge() {
return age;
}

}

// --- 使用 ---
Person p = new Person();
p.setAge(-5); // 无法非法赋值触发了校验逻辑
System.out.println(p.getAge()); // 输出 18

⭐⭐ 继承 (Inheritance)代码复用建立关系

子类可以继承父类的属性和方法避免代码重复

  • 代码复用公共的代码写在父类里子类直接拥有
  • 可扩展性子类可以在父类基础上增加自己特有的功能
  • 多态的基础没有继承就没有多态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 父类 (基类)
public class Animal {
protected String name; // protected 子类可访问

public void eat() {
System.out.println(name + " 在吃东西");
}
}

// 子类 (派生类)
public class Dog extends Animal {

// 子类特有方法
public void bark() {
System.out.println(name + " 在汪汪叫");
}

// 1. 方法重写 (Override)子类觉得父类的实现不够好自己重写
@Override
public void eat() {
System.out.println(name + " 在啃骨头"); // 更具体的实现
}
}

// --- 使用 ---
Dog dog = new Dog();
dog.name = "旺财";
dog.eat(); // 调用的是 Dog 重写后的方法旺财 在啃骨头
dog.bark(); // 调用子类特有方法

在 Java 语言中java.lang.Object 类是所有类的始祖如果一个类在定义时没有使用 extends 关键字明确指定其父类那么它会自动继承 Object 类这意味着你编写的任何一个类都默认直接或间接地继承自 Object所有对象包括数组都拥有 Object 类定义的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// Object 类 equals(Object obj) 方法判断两个对象的内容是否相等默认是比较内存地址
class Person {
String name;
int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

// 重写 equals 方法只要姓名和年龄相同就认为是同一个人
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 地址相同直接返回true
if (obj == null || getClass() != obj.getClass()) return false;

Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
}

// 测试
Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);
System.out.println(p1.equals(p2)); // 输出: true (如果不重写结果会是 false)


// Object 类 hashCode() 方法返回对象的哈希值主要配合 HashMapHashSet 使用
class Person {
String name;
int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

// 重写 hashCode如果两个对象 equals 为 true它们的 hashCode 必须相等
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}

// 测试
Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);
System.out.println(p1.hashCode() == p2.hashCode()); // 输出: true

⭐⭐ 多态 (Polymorphism)同一操作多种形态

同一个父类类型的引用指向不同的子类对象从而在执行时产生不同的行为它是建立在继承或实现接口和方法重写之上的

  • 灵活性接口通用实现各异
  • 可扩展性新增功能只需添加新子类无需修改已有调用逻辑
1
2
3
4
5
6
7
// 父类引用 animal
Animal animal1 = new Dog(); // 指向 Dog 对象
Animal animal2 = new Cat(); // 指向 Cat 对象

// 调用同一个方法表现出不同的行为
animal1.eat(); // 输出旺财 在啃骨头 (实际执行 Dog 的 eat)
animal2.eat(); // 输出喵喵 在吃鱼 (实际执行 Cat 的 eat)

⭐⭐ 编译类型和运行类型

  • 编译时类型是变量声明时的类型身份证名字决定了你能调用哪些方法
  • 运行时类型是对象实际创建时的类型真实面目决定了方法具体执行哪段代码
  • 这种编译时看父类运行时看子类的机制就是 多态
1
2
3
4
5
6
7
8
9
10
// 获取运行时类型
// 第一种instanceof 运算符用于判断
if (myPet instanceof Dog) {
System.out.println("它其实是一只狗");
}

// 第二种getClass() 方法用于获取具体类
if (myPet instanceof Dog) {
System.out.println("它其实是一只狗");
}

⭐⭐ 动态绑定

动态绑定也叫运行时绑定指的是在程序运行期间JVM 根据对象的实际类型运行时类型来决定到底调用哪个具体的方法实现

  • 静态绑定反义词在编译阶段就决定了调用哪个方法比如 static 方法private 方法
  • 动态绑定在编译阶段不做决定只在运行阶段才做决定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 父类
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}

public static void sleep() {
System.out.println("动物在睡觉 (静态方法)");
}
}

// 子类
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪汪 (Dog重写了)");
}

// 注意静态方法不能被重写只能被隐藏
public static void sleep() {
System.out.println("狗在睡觉 (静态方法)");
}
}

// 测试类
public class DynamicBindingDemo {
public static void main(String[] args) {
Animal animal = new Dog();

// 1. 动态绑定示例 (实例方法)
animal.makeSound();
// 输出汪汪汪
// 解析编译看 Animal (有该方法通过)运行看 Dog (执行 Dog 的重写方法)

// 2. 静态绑定示例 (静态方法)
animal.sleep();
// 输出动物在睡觉 (静态方法)
// 解析静态方法看引用类型 (Animal)不看实际对象 (Dog)所以不发生动态绑定
}
}

动态绑定只针对方法不针对属性成员变量

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
String name = "动物";
}
class Dog extends Animal {
String name = "狗";
}

Animal animal = new Dog();
System.out.println(animal.name);
// 输出动物
// 解析属性访问永远看引用的编译时类型 (Animal)不看运行时类型
// 这种现象叫变量隐藏 (Variable Hiding)不是覆盖

包的应用

包是用于组织类和接口的命名空间机制你可以把它想象成电脑中的文件夹而类就是文件夹里的文件25包的核心作用是解决命名冲突控制访问权限以及让项目结构更清晰

⭐⭐ 为什么要用包

如果没有包所有的类都挤在一起就像桌面上堆满了成千上万个文件不仅难以管理还容易重名

  • 解决命名冲突不同公司或开发者编写的类可能重名比如都写了一个 UserService只要将它们放在不同的包中如 com.companyA.service.UserService 和 com.companyB.service.UserService就可以共存
  • 访问控制包提供了一种访问级别默认/default 和 protected允许类之间在包内自由访问而对外隐藏细节
  • 模块化管理将功能相关的类组织在一起如数据库相关的放 dao 包业务逻辑放 service 包项目结构一目了然

⭐⭐ 包的命名规范

包名通常全部使用小写字母以确保与类名通常大写开头区分开

  • 反向域名这是最标准最推荐的命名方式因为域名是全球唯一的用域名倒过来作为包名前缀可以保证包名的唯一性
    • 格式域名后缀.公司名.项目名.模块名
    • 示例如果你的公司域名是 example.com项目叫 shop用户模块就可以命名为 com.example.shop.user
  • 层级分隔包的层级之间用英文句点 . 分隔这对应着文件系统中的目录结构
  • com.example.demo 对应的文件夹路径是 com/example/demo

⭐⭐ 创建和使用包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 创建包使用 package 关键字且必须放在源文件的第一行注释和空行除外
package com.example.animal; // 声明该类属于 com.example.animal 包

public class Dog {
public void bark() {
System.out.println("汪汪");
}
}


// 导入包如果你想在 com.example.demo 包中使用上面的 Dog 类就需要导入它
package com.example.demo;
import com.example.animal.Dog; // 第一种导入 animal 包下的 Dog 类

public class Main {
public static void main(String[] args) {
Dog dog = new Dog(); // 直接使用
}
}


// 第二种导入整个包不推荐容易造成命名冲突
import com.example.animal.*; // 导入 animal 包下的所有类


// 第三种如果不写 import也可以直接用全名调用但代码会显得冗长
com.example.animal.Dog dog = new com.example.animal.Dog();

访问控制

为了保证数据安全Java 提供了访问修饰符来控制属性的可见性核心原则是高内聚低耦合尽量隐藏内部数据

修饰符 权限 描述 用途
private 私有 只有在本类内部可以访问 最常用用于实现封装防止外部随意修改数据
public 公有 任何地方都可以访问 慎用直接暴露属性风险很大别人可以随意修改数据
protected 受保护 本包内和子类中可以访问 当你设计一个父类希望子类能够重写某些方法或直接调用某些父类逻辑但又不想让这些方法被无关的外部类随意调用时可以使用
不写修饰符 默认包私有 只有在同一个包内可以访问 当你有一组紧密相关的类放在同一个包内时它们之间需要共享一些数据或方法但又不希望暴露给整个项目这时用默认修饰符最合适

⭐⭐ 私有

规则 只能在本类内部访问外部完全不可见

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 类 A
class ClassA {
private void privateMethod() {
System.out.println("我是私有的只有本类能访问");
}

public void callFromInside() {
privateMethod(); // 合法本类内部可以调用
}
}

// 类 B
class ClassB {
public void test() {
ClassA a = new ClassA();
// a.privateMethod();
// 编译错误privateMethod 是私有的外部类看不见
}
}

⭐⭐ 公有

规则 任何地方都可以访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 在 com.example.utils 包中
package com.example.utils;

public class PublicClass {
public void doSomething() {
System.out.println("我是公开的谁都能调用");
}
}

// 在 com.example.main 包中
package com.example.main;
import com.example.utils.PublicClass;

public class Main {
public static void main(String[] args) {
PublicClass pc = new PublicClass();
pc.doSomething(); // 合法public 可以跨包访问
}
}

⭐⭐ 受保护

规则 同一个包内的类可以访问不同包的子类也可以访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 父类在 com.example.parent 包中
package com.example.parent;

public class Parent {
protected void protectedMethod() {
System.out.println("我是受保护的");
}
}

// 子类在 com.example.child 包中
package com.example.child;
import com.example.parent.Parent;

public class Child extends Parent {
public void test() {
protectedMethod(); // 合法不同包的子类可以访问父类的 protected 方法
}
}

// 普通类在 com.example.other 包中
package com.example.other;
import com.example.parent.Parent;

public class Other {
public void test() {
Parent p = new Parent();
// p.protectedMethod();
// 编译错误既不是子类也不在同包无法访问
}
}

⭐⭐ 默认

规则 不写任何修饰符只有同一个包内的类可以访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 在 com.example.package1 包中
package com.example.package1;

class DefaultClass { // 默认访问权限
void defaultMethod() {
System.out.println("我是默认权限只有同包能访问");
}
}

class SamePackageClass {
public void test() {
DefaultClass dc = new DefaultClass();
dc.defaultMethod(); // 合法同包内的类可以访问
}
}

// 在 com.example.package2 包中
package com.example.package2;

import com.example.package1.DefaultClass;

public class DifferentPackageClass {
public void test() {
DefaultClass dc = new DefaultClass();
// dc.defaultMethod();
// 编译错误不同包无法访问默认权限的成员
}
}

代码块

⭐⭐ 局部代码块

在方法中定义的代码块用于限定变量的作用域

1
2
3
4
5
6
7
8
9
10
11
public class LocalBlockDemo {
public void method() {
// 局部代码块
{
int x = 10;
System.out.println("代码块内: x = " + x);
}
// 这里无法访问 x因为 x 只在代码块内有效
// System.out.println(x); // 编译错误
}
}

⭐⭐ 构造代码块

在类中直接定义的代码块每次创建对象时都会执行且在构造方法之前执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class ConstructorBlockDemo {
private String name;
private int age;

// 构造代码块
{
System.out.println("构造代码块执行");
this.name = "默认姓名";
this.age = 18;
}

// 无参构造方法
public ConstructorBlockDemo() {
System.out.println("无参构造方法执行");
System.out.println("name: " + name + ", age: " + age);
}

// 有参构造方法
public ConstructorBlockDemo(String name, int age) {
System.out.println("有参构造方法执行");
this.name = name;
this.age = age;
System.out.println("name: " + name + ", age: " + age);
}

public static void main(String[] args) {
new ConstructorBlockDemo();
System.out.println("---");
new ConstructorBlockDemo("张三", 25);
}
}
// 构造代码块执行
// 无参构造方法执行
// name: 默认姓名, age: 18
// ---
// 构造代码块执行
// 有参构造方法执行
// name: 张三, age: 25

⭐⭐ 静态代码块

使用 static 关键字修饰的代码块在类加载时执行只执行一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class StaticBlockDemo {
private static String databaseUrl;
private static int connectionCount;

// 静态代码块 - 用于初始化静态变量
static {
System.out.println("静态代码块执行");
databaseUrl = "jdbc:mysql://localhost:3306/mydb";
connectionCount = 10;
System.out.println("数据库连接池初始化完成");
}

// 构造代码块
{
System.out.println("构造代码块执行");
}

public StaticBlockDemo() {
System.out.println("构造方法执行");
}

public static void main(String[] args) {
System.out.println("main方法开始执行");
new StaticBlockDemo();
new StaticBlockDemo();
}
}
// 静态代码块执行
// 数据库连接池初始化完成
// main方法开始执行
// 构造代码块执行
// 构造方法执行
// 构造代码块执行
// 构造方法执行

⭐⭐ 同步代码块

用于多线程同步保证同一时刻只有一个线程可以执行该代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class SynchronizedBlockDemo {
private static int count = 0;
private static Object lock = new Object();

public void increment() {
// 同步代码块使用 lock 对象作为锁
synchronized (lock) {
count++;
System.out.println(Thread.currentThread().getName() + ": count = " + count);
}
}

public static void main(String[] args) {
SynchronizedBlockDemo demo = new SynchronizedBlockDemo();

// 创建多个线程同时访问
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 3; j++) {
demo.increment();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "线程-" + i).start();
}
}
}

⭐⭐ 代码块执行顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Parent {
// 父类静态代码块
static {
System.out.println("1. 父类静态代码块");
}

// 父类构造代码块
{
System.out.println("3. 父类构造代码块");
}

// 父类构造方法
public Parent() {
System.out.println("4. 父类构造方法");
}
}

public class CodeBlockOrderDemo extends Parent {
// 子类静态代码块
static {
System.out.println("2. 子类静态代码块");
}

// 子类构造代码块
{
System.out.println("5. 子类构造代码块");
}

// 子类构造方法
public CodeBlockOrderDemo() {
System.out.println("6. 子类构造方法");
}

public static void main(String[] args) {
System.out.println("开始创建对象...");
new CodeBlockOrderDemo();
System.out.println("创建第二个对象...");
new CodeBlockOrderDemo();
}
}
// 1. 父类静态代码块
// 2. 子类静态代码块
// 开始创建对象...
// 3. 父类构造代码块
// 4. 父类构造方法
// 5. 子类构造代码块
// 6. 子类构造方法
// 创建第二个对象...
// 3. 父类构造代码块
// 4. 父类构造方法
// 5. 子类构造代码块
// 6. 子类构造方法

内部类

内部类是指定义在另一个类内部的类内部类的核心价值在于实现更好的封装解决多重继承的局限以及让代码结构更紧密可以把外部类看作一个整体而内部类则是这个整体中的组成部分辅助工具

  • 实现多重继承的效果Java 不支持类的多重继承即一个类不能同时继承两个类但通过内部类可以让外部类继承一个类同时让内部类继承另一个类从而在逻辑上实现多重继承
  • 更好的封装性将一个类隐藏在另一个类内部不让外部其他类直接访问只有外部类可以控制它的行为提高了代码的安全性和内聚性
  • 方便访问外部类成员内部类可以自由访问外部类的成员包括 private 私有成员而外部类访问内部类时需要通过内部类的对象

成员内部类

成员内部类是最普通的内部类定义在外部类的成员位置与成员变量和方法同级

  • 依附于外部类实例必须先创建外部类对象才能创建内部类对象
  • 访问权限可以直接访问外部类的所有成员包括私有成员
  • 不能定义静态成员除非是 static final 修饰的常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Outer {
private String msg = "Hello";

// 成员内部类
public class Inner {
public void print() {
// 直接访问外部类的私有成员
System.out.println(msg);
}
}
}

// 使用
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // 必须通过外部类实例创建
inner.print();

静态内部类

静态内部类也称为嵌套类定义在外部类的成员位置但使用 static 修饰

  • 不依附外部类实例可以直接创建对象不需要外部类对象
  • 访问限制只能访问外部类的静态成员不能直接访问外部类的非静态成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Outer {
private static String staticMsg = "Static Hello";

// 静态内部类
public static class StaticInner {
public void print() {
System.out.println(staticMsg); // 只能访问静态成员
}
}
}

// 使用
Outer.StaticInner inner = new Outer.StaticInner(); // 无需外部类实例
inner.print();

局部内部类

局部内部类定义在外部类的方法或代码块内部

  • 作用域局限只能在定义它的方法内部使用方法执行完后即失效
  • 访问局部变量只能访问方法中被 final 修饰或实际上为 finalJava 8+即赋值后未改变的局部变量
  • 不能有访问修饰符不能用 publicprivate 等修饰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Outer {
public void method() {
final int localVar = 10; // effectively final

// 局部内部类
class LocalInner {
public void print() {
System.out.println(localVar); // 只能访问final或effectively final变量
}
}

new LocalInner().print(); // 只能在本方法内使用
}
}

匿名内部类

没有名字的内部类通常用于实现接口或继承类并且只使用一次

  • 没有类名通常在创建对象时直接定义类体
  • 必须继承或实现必须继承一个类或实现一个接口
  • 常用于回调/监听比如按钮点击事件
1
2
3
4
5
6
7
8
9
10
11
12
13
// 假设有一个接口
interface OnClickListener {
void onClick();
}

// 使用匿名内部类
OnClickListener listener = new OnClickListener() {
@Override
public void onClick() {
System.out.println("按钮被点击了");
}
};
listener.onClick();

多重继承

Java 不支持多重继承但内部类可以完美解决这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class A { 
public void a() {
System.out.println("A");
}
}

class B {
public void b() {
System.out.println("B");
}
}

// C 继承了 A
class C extends A {

public void callA() {
a();
}

// C 通过内部类继承 B
private class InnerB extends B {
public void callB() {
b(); // 可以调用 B 的方法
}
}

// C 提供一个方法来使用 InnerB
public void useB() {
new InnerB().callB();
}
}

// 测试
C c = new C();
c.callA(); // 输出 A
c.useB(); // 输出 B

抽象类

抽象类是一种不能被直接实例化的特殊类它就像是一个半成品模板主要用于为子类提供统一的结构和规范强制子类实现特定的方法同时也可以提供可复用的公共代码抽象类通过 abstract 关键字来定义

抽象类的核心价值在于代码复用和定义规范

  • 提取共性将多个子类共有的属性和方法提取到抽象父类中减少代码重复
  • 强制约束通过定义抽象方法强制要求所有子类必须提供自己的具体实现确保子类具备某些特定行为
  • 提供默认实现抽象类中可以包含普通方法具体方法为子类提供通用的可选的默认行为
  • 不能直接实例化抽象类不能使用 new 关键字直接创建对象

核心语法

使用 abstract 修饰的类就是抽象类修饰的方法就是抽象方法抽象方法是一种没有方法体只有声明的方法它存在的意义就是被子类重写

  • 抽象方法不能是 privateprivate 修饰的方法对子类不可见
  • 抽象方法不能是 finalfinal 修饰的方法不能被重写
  • 抽象方法不能是 staticstatic 修饰的方法属于类本身
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 抽象类
abstract class Animal {
// 普通成员变量
protected String name;

// 构造方法
public Animal(String name) {
this.name = name;
}

// 普通方法具体方法有方法体
public void sleep() {
System.out.println(name + " 正在睡觉");
}

// 抽象方法没有方法体以分号结尾
public abstract void makeSound();
}

子类继承

当一个普通类非抽象类继承一个抽象类时它必须重写父类中的所有抽象方法如果子类不想实现那么子类也必须声明为抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 抽象类
abstract class Animal {
// 普通成员变量
protected String name;

// 抽象方法没有方法体以分号结尾
public abstract void makeSound();
}

// 子类
class Dog extends Animal {
public Dog(String name) {
super(name);
}

// 必须重写 makeSound 抽象方法
@Override
public void makeSound() {
System.out.println(name + " 汪汪叫");
}
}

构造方法

虽然不能直接实例化但抽象类可以定义构造方法它的构造方法主要用于被子类的构造方法调用通过 super 关键字以初始化抽象类中定义的成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// 1. 定义抽象类
abstract class Employee {
protected String name;
protected String employeeId;

// 抽象类的构造方法
// 作用初始化所有员工共有的属性姓名工号
public Employee(String name, String employeeId) {
this.name = name;
this.employeeId = employeeId;
System.out.println("抽象父类构造方法正在创建员工: " + name + ", 工号: " + employeeId);
}

// 抽象方法具体的工作内容由子类决定
public abstract void doWork();

// 普通方法所有员工通用的行为
public void clockIn() {
System.out.println(name + " (" + employeeId + ") 打卡上班");
}
}

// 2. 具体子类工程师
class Engineer extends Employee {
private String programmingLanguage;

// 子类构造方法
// 必须通过 super() 调用父类的构造方法
public Engineer(String name, String employeeId, String language) {
super(name, employeeId); // 👈 调用抽象父类的构造方法
this.programmingLanguage = language;
System.out.println("子类构造方法工程师 " + name + " 擅长 " + language);
}

@Override
public void doWork() {
System.out.println(name + " 正在编写 " + programmingLanguage + " 代码");
}
}

// 3. 具体子类经理
class Manager extends Employee {
private int teamSize;

public Manager(String name, String employeeId, int size) {
super(name, employeeId); // 👈 调用抽象父类的构造方法
this.teamSize = size;
System.out.println("子类构造方法经理 " + name + " 管理 " + teamSize + " 人团队");
}

@Override
public void doWork() {
System.out.println(name + " 正在开会并制定团队计划");
}
}

// 4. 测试类
public class PolymorphismDemo {
public static void main(String[] args) {
System.out.println("=== 创建工程师对象 ===");
// 多态父类引用指向子类对象
Employee emp1 = new Engineer("张工", "E001", "Java");

System.out.println("\n=== 创建经理对象 ===");
Employee emp2 = new Manager("李经理", "M001", 10);

System.out.println("\n=== 通过多态调用方法 ===");
// 调用普通方法继承自父类
emp1.clockIn(); // 输出: 张工 (E001) 打卡上班
emp2.clockIn(); // 输出: 李经理 (M001) 打卡上班

// 调用抽象方法动态绑定执行子类重写后的方法
emp1.doWork(); // 输出: 张工 正在编写 Java 代码
emp2.doWork(); // 输出: 李经理 正在开会并制定团队计划
}
}

接口

接口是一种极其重要的引用数据类型它是一种比抽象类更加纯粹的抽象如果说抽象类定义了是什么的基础结构那么接口则定义了能做什么的行为规范或契约接口使用 interface 关键字来定义

  • 实现多态接口是实现多态的重要手段通过接口类型的引用指向具体的实现类对象可以让程序在运行时动态决定调用哪个类的方法
  • 解决单继承局限Java 类只能单继承但一个类可以实现多个接口这使得对象可以具备多种类型的行为能力变相实现了多重继承
  • 实现解耦接口将定义实现分离调用者只依赖于接口不依赖于具体的实现类降低了模块间的耦合度提高了系统的可扩展性和可维护性
  • 定义标准与规范接口就像一份契约规定了所有实现类必须提供哪些方法确保了不同类之间的行为一致性

接口常量

接口中的变量会被隐式地指定为 public static final即公共的静态的不可变的常量

1
2
3
4
public interface Constants {
// 等同于 public static final int VALUE = 100;
int VALUE = 100;
}

抽象方法

接口中的方法默认是 public abstract不需要手动添加修饰符

1
2
3
4
public interface Flyable {
// 等同于 public abstract void fly();
void fly();
}

默认方法

默认方法使用 default 修饰可以包含方法体这允许在不破坏现有实现类的情况下为接口新增方法如果一个类实现了两个包含同名 default 方法的接口必须在实现类中重写该方法并使用 接口名.super.方法名() 来指定调用哪一个

1
2
3
4
5
public interface Flyable {
default void land() {
System.out.println("默认的降落方式");
}
}

静态方法

静态方法使用 static 修饰可以直接通过接口名调用主要用于提供工具方法

1
2
3
4
5
6
7
public interface Flyable {
static void showInfo() {
System.out.println("这是一个飞行接口");
}
}
// 调用
Flyable.showInfo();

私有方法

私有方法使用 private 修饰用于在接口内部封装 default 方法或 static 方法的公共逻辑不能被实现类调用

1
2
3
4
5
6
7
8
9
10
11
public interface Flyable {
private void helper() {
// 共用的辅助逻辑
}
default void methodA() {
helper();
}
default void methodB() {
helper();
}
}

多重继承

一个类可以实现多个接口从而具备多种能力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface Flyable {
void fly();
}

interface Swimmable {
void swim();
}

// 鸭子既会飞又会游泳
class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("鸭子扇动翅膀飞行");
}

@Override
public void swim() {
System.out.println("鸭子用脚蹼游泳");
}
}

接口继承

接口之间可以使用 extends 关键字进行继承并且支持多继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface A { 
void methodA();
}

interface B {
void methodB();
}

// 接口 C 继承了 A 和 B
interface C extends A, B {
void methodC();
}

class Impl implements C {
@Override
public void methodA() { }
@Override
public void methodB() { }
@Override
public void methodC() { }
}

应用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 定义一个计算策略接口
interface Calculator {
int calculate(int a, int b);
}

// 加法实现
class AddCalculator implements Calculator {
@Override
public int calculate(int a, int b) {
return a + b;
}
}

// 减法实现
class SubCalculator implements Calculator {
@Override
public int calculate(int a, int b) {
return a - b;
}
}

// 使用多态
public class Test {
public static void main(String[] args) {
Calculator calc; // 接口引用

calc = new AddCalculator();
System.out.println(calc.calculate(5, 3)); // 输出 8

calc = new SubCalculator();
System.out.println(calc.calculate(5, 3)); // 输出 2
}
}

枚举

枚举是一种特殊的类用于定义一组固定的命名的常量集合它不仅仅是一个常量列表更是一种强大的类型安全工具能够有效替代传统的 public static final 常量定义方式枚举使用 enum 关键字定义

  • 类型安全编译器会检查枚举类型杜绝非法值
  • 可读性强语义清晰直接使用 Season.SPRING
  • 功能强大可以像普通类一样拥有成员变量方法甚至实现接口

核心语法

1
2
3
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER;
}

构造函数

枚举可以包含成员变量和构造函数但构造函数必须是私有的 private 可省略以防止外部创建实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public enum Season {
// 枚举常量传入构造函数参数
SPRING("春天", 1),
SUMMER("夏天", 2);

// 成员变量
private final String name;
private final int value;

// 构造函数自动为 private
Season(String name, int value) {
this.name = name;
this.value = value;
}

// Getter 方法
public String getName() { return name; }
public int getValue() { return value; }
}

抽象方法

枚举可以定义抽象方法让每个枚举常量提供自己的具体实现这非常适合实现策略模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public enum Operation {
ADD {
public int apply(int a, int b) {
return a + b;
}
},
SUBTRACT {
public int apply(int a, int b) {
return a - b;
}
};

// 定义抽象方法
public abstract int apply(int a, int b);
}

// 使用
int result = Operation.ADD.apply(5, 3); // 返回 8

常用方法

Java 编译器会自动为枚举类生成一些静态方法

方法 说明 示例
values() 返回枚举类型的所有常量数组按定义顺序 Season[] seasons = Season.values();
valueOf(String) 根据名称返回枚举实例名称必须完全匹配否则抛异常 Season s = Season.valueOf("SPRING");
name() 返回枚举常量的名称字符串 String name = Season.SPRING.name();
ordinal() 返回枚举常量的序号从 0 开始 int index = Season.SPRING.ordinal();

应用示例

⭐⭐ 状态机与状态管理

这是枚举最常见的场景如订单状态审核状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public enum OrderStatus {
PENDING("待支付"),
PAID("已支付"),
SHIPPED("已发货"),
COMPLETED("已完成");

private final String desc;

OrderStatus(String desc) {
this.desc = desc;
}

public String getDesc() {
return desc;
}
}

⭐⭐ 策略模式

当代码中出现大量的 if-elseswitch 判断类型时可以用枚举策略替代

1
2
3
4
5
6
7
8
9
10
11
// 枚举方式定义行为
public enum PayStrategy {
ALI_PAY {
public void pay() { /* 支付宝逻辑 */ }
},
WECHAT_PAY {
public void pay() { /* 微信逻辑 */ }
};

public abstract void pay();
}

⭐⭐ 线程安全的单例模式

枚举是实现单例模式的最佳方式之一不仅代码简洁而且天然防止反射和反序列化攻击绝对线程安全

1
2
3
4
5
6
7
8
9
10
public enum Singleton {
INSTANCE;

public void doSomething() {
System.out.println("执行业务逻辑");
}
}

// 使用
Singleton.INSTANCE.doSomething();

泛型

Java 泛型是 Java SE 5.0 引入的一项重大特性它提供了编译时类型安全检测机制允许程序员在编译时检测到非法的类型操作泛型的本质是参数化类型即所操作的数据类型被指定为一个参数

标识符

在 Java 泛型中虽然可以使用任何合法的标识符作为类型参数名例如 MyType但为了代码的可读性和社区共识惯例是使用单个大写字母

字母 全称 含义 示例
T Type 最常用代表一个未知的具体类型
适用于泛型类泛型方法泛型接口
public <T> T getValue()
E Element 集合专用代表集合中的元素类型
适用于实现 `Collection`, `List`, `Set` 等集合类时
class MyList<E> implements List<E>
K Key 键专用代表映射Map中的键类型
适用于`Map`, `Dictionary` 等相关类
class MyMap<K, V>
V Value 值专用代表映射Map中的值类型
通常与 `K` 成对出现
class MyMap<K, V>
N Number 数字专用代表数值类型
适用于需要限制类型为数字及其子类Integer, Double 等的场景
class Stats<N extends Number>
R Return / Result 返回值常用于函数式接口或回调中表示返回结果的类型 Function<T, R> (输入 T, 返回 R)
S, U, V... 序列 第二个第三个类型参数
当 `T` 被占用或者需要多个不同类型的参数时按字母表顺序向后推演
TriFunction<T, U, V>

泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// T 代表类型参数 (Type)
public class Box<T> {
private T content;

public void set(T content) {
this.content = content;
}

public T get() {
return content;
}
}

// 使用
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String s = stringBox.get();

泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
public class Util {
// <T> 声明这是一个泛型方法T 是类型参数
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}

// 调用时通常可以自动推断类型
String[] strArr = {"A", "B", "C"};
Util.printArray(strArr);
}

泛型接口

1
2
3
4
5
6
7
8
9
10
public interface Generator<T> {
T next();
}

public class StringGenerator implements Generator<String> {
@Override
public String next() {
return "随机字符串";
}
}

通配符

⭐⭐ 无限定通配符 (?)

表示未知类型通常用于读取操作但不能向其中添加元素除了 null

1
2
3
4
5
6
public void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem);
}
// list.add("Error"); // 编译报错因为不知道具体类型
}

⭐⭐ 上界通配符 (? extends T)

表示类型必须是 T 或 T 的子类

  • 原则PECS (Producer Extends, Consumer Super)如果参数化类型作为一个生产者只读/输出数据使用 extends
  • 限制不能向集合中添加元素因为不知道具体是哪个子类只能读取为 T 类型
1
2
3
4
5
6
7
8
9
10
// 接受 List<Fruit> 或 List<Apple> (假设 Apple 继承 Fruit)
public double sumOfFruits(List<? extends Fruit> fruits) {
double sum = 0;
for (Fruit f : fruits) { // 可以读取为 Fruit
sum += f.getPrice();
}
// 编译报错传入的参数可能是 List<Orange>
// fruits.add(new Apple());
return sum;
}

⭐⭐ 下界通配符 (? super T)

表示类型必须是 T 或 T 的父类

  • 原则如果参数化类型作为一个消费者写入/输入数据使用 super
  • 限制读取时只能得到 Object 类型因为可能是很远的父类但可以安全地写入 T 及其子类
1
2
3
4
5
6
7
// 接受 List<Fruit> 或 List<Object>
public void addFruits(List<? super Fruit> fruits) {
fruits.add(new Apple()); // 安全因为列表至少是 Fruit 或其父类
fruits.add(new Fruit());

// Object obj = fruits.get(0); // 只能作为 Object 读取
}

⭐⭐ 多重边界

类型参数可以同时被多个接口限定但只能有一个类且必须在第一位

1
2
3
public <T extends Comparable<T> & Serializable> void sortAndSave(T item) {
// ...
}

类型擦除

Java 的泛型是伪泛型泛型信息只存在于代码编译阶段一旦编译成 .class 文件泛型类型参数就会被擦除替换为其限定类型如果没有指定限定则替换为 Object

为了保持多态性编译器会在子类中生成桥接方法例如泛型类重写父类方法时编译器会生成一个签名包含 Object 的桥接方法来调用具体的泛型方法

  • List<String> 和 List<Integer> 在运行时是同一个类 (List.class)
  • 不能使用 new T() 实例化泛型类型
  • 不能创建泛型数组 new T[]
  • 不能使用 instanceof 检查泛型类型如 obj instanceof List<String> 是非法的只能 obj instanceof List

⭐⭐ 常见误区与限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 基本类型不能作为类型参数
// 错误List<int> list = new ArrayList<>();
List<Integer> list = new ArrayList<>(); //(需使用包装类)


// 泛型数组创建受限
// 数组在运行时会检查类型而泛型在运行时已擦除会导致类型安全检查冲突
// 错误List<String>[] ls = new List<String>[10];
// 使用List<List<String>> 或 ArrayList<?>[] (非类型安全需抑制警告)


// 静态上下文中不能使用类级别的类型参数
public class GenericTest<T> {
// 编译报错静态变量属于类不属于某个具体的泛型实例
private static T t;
}
// 解决将静态方法改为泛型方法 <T> void method(T t)


// 异常不能使用泛型
// 不能 catch (T e)因为异常处理是在运行时进行的而泛型信息在运行时已丢失

反射

Java 反射是 Java 语言中一种非常强大但需要谨慎使用的机制它允许程序在运行时动态地获取类的信息并操作类或对象的属性和方法打破了 Java 作为静态语言在编译期就必须确定所有类型的限制

  • 获取类的结构信息获取类的构造器方法字段注解父类接口等
  • 动态创建对象不仅限于无参构造甚至可以调用私有构造器
  • 动态调用方法调用任意对象的任意方法包括私有方法
  • 动态访问和修改属性读写任意对象的任意字段包括私有字段
  • 破坏封装性通过 setAccessible(true) 突破 private 等访问修饰符的限制
核心类 作用 获取方式
Class 类的元数据是反射的入口 Class.forName("全类名")
Field 描述类的成员变量 clazz.getDeclaredField("name")
Method 描述类的方法 clazz.getDeclaredMethod("methodName")
Constructor 描述类的构造器 clazz.getDeclaredConstructor(String.class)

核心语法

⭐⭐ 第一种类名.class

1
2
// 适用于编译期已知类型
Class<User> clazz = User.class;

⭐⭐ 第二种对象.getClass()

1
2
3
// 适用于已知对象实例
User user = new User();
Class<?> clazz = user.getClass();

⭐⭐ 第三种Class.forName("全限定类名")

1
2
// 最常用适用于运行时动态加载如读取配置文件
Class<?> clazz = Class.forName("com.example.User");

创建对象

1
2
3
4
5
6
7
8
9
// 获取 Class 对象
Class<?> clazz = Class.forName("com.example.User");

// 调用无参构造器 (推荐写法)
Object obj = clazz.getDeclaredConstructor().newInstance();

// 调用有参构造器
Constructor<?> ctor = clazz.getConstructor(String.class, int.class);
Object obj2 = ctor.newInstance("张三", 25);

操作属性

1
2
3
4
5
6
7
8
9
10
11
// 获取私有字段
Field field = clazz.getDeclaredField("name");

// 突破 private 限制
field.setAccessible(true);

// 设置值
field.set(obj, "李四");

// 获取值
String name = (String) field.get(obj);

调用方法

1
2
3
4
5
6
7
8
// 获取方法 (public 方法用 getMethod, private 用 getDeclaredMethod)
Method method = clazz.getDeclaredMethod("sayHello");

// 突破 private 限制
method.setAccessible(true);

// 调用方法 (第一个参数是对象实例后面是方法参数)
method.invoke(obj);

注解

注解是一种特殊的接口用于为代码添加元数据信息修饰解释 包方法属性构造器局部变量等数据它不会直接影响程序的运行逻辑但可以被编译器开发工具或框架所使用

元注解

⭐⭐ @Retention

指定注解的保留策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public enum RetentionPolicy {
SOURCE, // 只存在于源代码中编译时被丢弃
CLASS, // 存在于编译后的class文件中但运行时不可见默认值
RUNTIME // 运行时可见可通过反射获取
}

// 示例
@Retention(RetentionPolicy.SOURCE)
@interface SourceAnnotation {} // 只在源代码中有效

@Retention(RetentionPolicy.CLASS)
@interface ClassAnnotation {} // 编译后保留运行时不可见

@Retention(RetentionPolicy.RUNTIME)
@interface RuntimeAnnotation {} // 运行时可见

⭐⭐ @Target

指定注解可以使用的目标位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public enum ElementType {
TYPE, // 类接口枚举
FIELD, // 字段
METHOD, // 方法
PARAMETER, // 参数
CONSTRUCTOR, // 构造函数
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE,// 注解类型
PACKAGE, // 包
TYPE_PARAMETER, // 类型参数Java 8
TYPE_USE // 类型使用Java 8
}

// 示例
@Target({ElementType.TYPE, ElementType.METHOD})
@interface MyTargetAnnotation {}

// 使用
@MyTargetAnnotation // 可以用于类
public class TargetDemo {
@MyTargetAnnotation // 可以用于方法
public void method() {}

// @MyTargetAnnotation // 不能用于字段会编译错误
private String field;
}

⭐⭐ @Documented

使注解信息出现在Javadoc中

1
2
3
4
5
6
7
8
9
10
11
@Documented
@interface DocumentedAnnotation {}

/**
* 测试类
* @author 作者
*/
@DocumentedAnnotation
public class DocumentedDemo {
// 在生成的Javadoc中会包含注解信息
}

⭐⭐ @Inherited

允许子类继承父类的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface InheritedAnnotation {}

@InheritedAnnotation
class Parent {}

class Child extends Parent {} // Child也拥有InheritedAnnotation注解

public class InheritedDemo {
public static void main(String[] args) {
// 检查Child是否有注解
boolean hasAnnotation = Child.class.isAnnotationPresent(InheritedAnnotation.class);
System.out.println("Child是否有注解: " + hasAnnotation); // true
}
}

内置注解

⭐⭐ @Override

表示方法重写父类方法编译器会检查该方法是否真正重写了父类方法

1
2
3
4
5
6
7
8
9
10
11
12
13
class Parent {
public void method() {}
}

class Child extends Parent {
@Override
public void method() { // 正确重写
System.out.println("子类方法");
}

// @Override
// public void method2() {} // 编译错误父类没有method2方法
}

⭐⭐ @Deprecated

标记已过时的方法或类使用时会显示警告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DeprecatedDemo {
@Deprecated
public void oldMethod() {
System.out.println("这是一个过时的方法");
}

@Deprecated(since = "2.0", forRemoval = true)
public void oldMethodForRemoval() {
System.out.println("即将被移除的方法");
}

public void newMethod() {
System.out.println("推荐使用的新方法");
}

public static void main(String[] args) {
DeprecatedDemo demo = new DeprecatedDemo();
demo.oldMethod(); // 会有编译警告
demo.oldMethodForRemoval(); // 更强的警告
}
}

⭐⭐ @SuppressWarnings

抑制编译警告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SuppressWarningsDemo {
@SuppressWarnings("unchecked") // 抑制未检查类型转换警告
public void useRawType() {
List list = new ArrayList(); // 不会产生警告
list.add("string");
}

@SuppressWarnings({"unchecked", "deprecation"}) // 抑制多个警告
public void suppressMultipleWarnings() {
DeprecatedDemo demo = new DeprecatedDemo();
demo.oldMethod(); // 不会产生过时方法警告
}

@SuppressWarnings("all") // 抑制所有警告
public void suppressAllWarnings() {
// 所有警告都被抑制
}
}

⭐⭐ @SafeVarargs

抑制可变参数类型的安全警告

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SafeVarargsDemo {
@SafeVarargs
public static <T> void printElements(T... elements) {
for (T element : elements) {
System.out.println(element);
}
}

@SafeVarargs
public final <T> List<T> createList(T... elements) {
return Arrays.asList(elements);
}
}

⭐⭐ @FunctionalInterface

标记函数式接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@FunctionalInterface
interface Calculator {
int calculate(int a, int b); // 只有一个抽象方法

// 可以有默认方法
default void print() {
System.out.println("计算结果");
}

// 可以有静态方法
static void info() {
System.out.println("这是一个函数式接口");
}
}

自定义注解

⭐⭐ 基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.lang.annotation.*;

// 元注解 - 定义注解的行为
@Retention(RetentionPolicy.RUNTIME) // 注解保留策略
@Target(ElementType.METHOD) // 注解使用目标
@Documented // 是否生成文档
@Inherited // 是否允许继承
public @interface MyAnnotation {
// 注解属性看起来像方法
String value() default ""; // 默认值为空字符串
int priority() default 1; // 默认优先级为1
String[] tags() default {}; // 数组类型属性
Class<?> type() default Object.class; // Class类型属性
}

⭐⭐ 注解属性类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public @interface CompleteAnnotation {
// 基本类型
int intValue() default 0;
boolean booleanValue() default true;
char charValue() default 'A';

// String类型
String stringValue() default "";

// Class类型
Class<?> classValue() default Object.class;

// 枚举类型
enum Priority { LOW, MEDIUM, HIGH }
Priority priority() default Priority.MEDIUM;

// 注解类型
AnotherAnnotation anotherAnnotation() default @AnotherAnnotation;

// 数组类型
String[] tags() default {};

// 特殊属性value
String value() default ""; // 如果只有一个value属性使用时可以省略属性名
}

⭐⭐ 自定义重复注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 定义容器注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Roles {
Role[] value();
}

// 定义可重复的注解
@Repeatable(Roles.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Role {
String value();
}

// 使用重复注解
@Role("ADMIN")
@Role("USER")
@Role("MANAGER")
class MultiRoleUser {
// 等价于 @Roles({@Role("ADMIN"), @Role("USER"), @Role("MANAGER")})
}

public class RepeatableAnnotationDemo {
public static void main(String[] args) {
// 获取重复注解
Role[] roles = MultiRoleUser.class.getAnnotationsByType(Role.class);
System.out.println("用户拥有的角色:");
for (Role role : roles) {
System.out.println(" - " + role.value());
}
}
}

注解处理

⭐⭐ 运行时注解处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class RuntimeProcessor {
public static void processAnnotation(Object obj) {
Class<?> clazz = obj.getClass();

// 处理类上的注解
for (Annotation annotation : clazz.getAnnotations()) {
System.out.println("类注解: " + annotation);
}

// 处理方法上的注解
for (Method method : clazz.getDeclaredMethods()) {
for (Annotation annotation : method.getAnnotations()) {
System.out.println("方法 " + method.getName() + " 的注解: " + annotation);
}
}

// 处理字段上的注解
for (Field field : clazz.getDeclaredFields()) {
for (Annotation annotation : field.getAnnotations()) {
System.out.println("字段 " + field.getName() + " 的注解: " + annotation);
}
}
}
}

⭐⭐ 编译时注解处理

1
2
3
4
5
6
7
8
9
10
//需要创建注解处理器
@SupportedAnnotationTypes("com.example.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 处理注解逻辑
return true;
}
}

实际应用

⭐⭐ 单元测试框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Test {
String description() default "";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface BeforeEach {}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface AfterEach {}

// 简单的测试框架
class TestRunner {
public static void runTests(Class<?> testClass) throws Exception {
Object instance = testClass.getDeclaredConstructor().newInstance();

// 执行所有 @BeforeEach 方法
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(BeforeEach.class)) {
method.invoke(instance);
}
}

// 执行所有 @Test 方法
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(Test.class)) {
try {
method.invoke(instance);
System.out.println("✓ " + method.getName() + " 测试通过");
} catch (Exception e) {
System.out.println("✗ " + method.getName() + " 测试失败: " + e.getCause().getMessage());
}
}
}

// 执行所有 @AfterEach 方法
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(AfterEach.class)) {
method.invoke(instance);
}
}
}
}

// 使用示例
class MyTest {
@BeforeEach
public void setup() {
System.out.println("执行测试前准备");
}

@AfterEach
public void cleanup() {
System.out.println("执行测试后清理");
}

@Test(description = "测试加法")
public void testAdd() {
int result = 2 + 3;
assert result == 5;
}

@Test(description = "测试减法")
public void testSubtract() {
int result = 5 - 3;
assert result == 2;
}
}

⭐⭐ 字段验证框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// 定义验证注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface NotNull {
String message() default "字段不能为空";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Min {
int value();
String message() default "值太小";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Max {
int value();
String message() default "值太大";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Email {
String message() default "邮箱格式不正确";
}

// 验证器
class Validator {
public static List<String> validate(Object obj) throws IllegalAccessException {
List<String> errors = new ArrayList<>();
Field[] fields = obj.getClass().getDeclaredFields();

for (Field field : fields) {
field.setAccessible(true);
Object value = field.get(obj);

// 验证 @NotNull
if (field.isAnnotationPresent(NotNull.class)) {
if (value == null) {
NotNull notNull = field.getAnnotation(NotNull.class);
errors.add(field.getName() + ": " + notNull.message());
}
}

// 验证 @Min 和 @Max
if (value instanceof Integer) {
int intValue = (int) value;

if (field.isAnnotationPresent(Min.class)) {
Min min = field.getAnnotation(Min.class);
if (intValue < min.value()) {
errors.add(field.getName() + ": " + min.message() + " (最小值: " + min.value() + ")");
}
}

if (field.isAnnotationPresent(Max.class)) {
Max max = field.getAnnotation(Max.class);
if (intValue > max.value()) {
errors.add(field.getName() + ": " + max.message() + " (最大值: " + max.value() + ")");
}
}
}

// 验证 @Email
if (field.isAnnotationPresent(Email.class) && value instanceof String) {
String email = (String) value;
if (!email.matches("^[A-Za-z0-9+_.-]+@(.+)$")) {
Email emailAnno = field.getAnnotation(Email.class);
errors.add(field.getName() + ": " + emailAnno.message());
}
}
}

return errors;
}
}

// 使用示例
class User {
@NotNull(message = "用户名不能为空")
private String username;

@Min(value = 18, message = "年龄必须大于等于18岁")
@Max(value = 120, message = "年龄必须小于等于120岁")
private int age;

@Email(message = "邮箱格式不正确")
@NotNull(message = "邮箱不能为空")
private String email;

// 构造方法gettersetter省略
public User(String username, int age, String email) {
this.username = username;
this.age = age;
this.email = email;
}
}

public class ValidationDemo {
public static void main(String[] args) throws IllegalAccessException {
User user = new User("张三", 15, "invalid-email");
List<String> errors = Validator.validate(user);

if (errors.isEmpty()) {
System.out.println("验证通过");
} else {
System.out.println("验证失败:");
errors.forEach(error -> System.out.println(" - " + error));
}
}
}

⭐⭐ 权限控制注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface RequiresRole {
String[] value();
Logical logical() default Logical.AND;
}

enum Logical {
AND, OR
}

// 权限检查器
class SecurityManager {
private Set<String> currentUserRoles = new HashSet<>();

public void login(String... roles) {
currentUserRoles.clear();
currentUserRoles.addAll(Arrays.asList(roles));
System.out.println("用户登录角色: " + currentUserRoles);
}

public boolean checkPermission(Method method) {
if (!method.isAnnotationPresent(RequiresRole.class)) {
return true; // 没有权限要求允许访问
}

RequiresRole requiresRole = method.getAnnotation(RequiresRole.class);
String[] requiredRoles = requiresRole.value();
Logical logical = requiresRole.logical();

if (logical == Logical.AND) {
// 需要同时拥有所有角色
return currentUserRoles.containsAll(Arrays.asList(requiredRoles));
} else {
// 只需要拥有其中一个角色
for (String role : requiredRoles) {
if (currentUserRoles.contains(role)) {
return true;
}
}
return false;
}
}
}

// 使用示例
class Service {
@RequiresRole("ADMIN")
public void adminOnly() {
System.out.println("执行管理员操作");
}

@RequiresRole(value = {"ADMIN", "MANAGER"}, logical = Logical.OR)
public void adminOrManager() {
System.out.println("执行管理员或经理操作");
}

@RequiresRole(value = {"ADMIN", "USER"}, logical = Logical.AND)
public void bothRoles() {
System.out.println("执行需要同时拥有ADMIN和USER角色的操作");
}

public void publicMethod() {
System.out.println("执行公开方法");
}
}

常用类库

Java 自带的类构成了其强大的标准库也称为 Java API 或 JDK 类库这些类按照功能被组织在不同的包Package掌握核心包的结构和常用类是进行 Java 开发的基础

异常

异常是程序运行过程中的意外状况它会打断代码原本的正常执行流程Java 的异常处理机制非常强大它不仅能让程序在遇到错误时优雅降级比如给出友好提示回滚事务还能帮助开发者快速定位问题根源

异常体系

Java 中所有的异常都继承自 java.lang.Throwable它是整个异常体系的顶层父类Throwable 有两个重要的子类ErrorException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
java.lang.Object

└── java.lang.Throwable (所有异常和错误的基类)

├── java.lang.Error (错误 - 不可恢复的系统级问题)
│ │
│ ├── 虚拟机错误
│ │ ├── VirtualMachineError
│ │ │ ├── StackOverflowError (栈溢出)
│ │ │ ├── OutOfMemoryError (内存溢出)
│ │ │ ├── InternalError (内部错误)
│ │ │ └── UnknownError (未知错误)
│ │ │
│ │ └── ThreadDeath (线程终止)
│ │
│ ├── 类加载错误
│ │ ├── NoClassDefFoundError (类定义未找到)
│ │ ├── ClassFormatError (类格式错误)
│ │ ├── UnsupportedClassVersionError (不支持的类版本)
│ │ └── IncompatibleClassChangeError (不兼容的类变更)
│ │
│ ├── 链接错误
│ │ ├── LinkageError (链接错误)
│ │ │ ├── NoClassDefFoundError
│ │ │ ├── ClassCircularityError (类循环依赖)
│ │ │ └── UnsatisfiedLinkError (不满足的链接)
│ │ │
│ │ └── ExceptionInInitializerError (初始化器异常)
│ │
│ ├── 断言错误
│ │ └── AssertionError (断言失败)
│ │
│ ├── AWT 错误
│ │ └── AWTError (AWT 错误)
│ │
│ └── 其他错误
│ ├── IOError (I/O 错误)
│ └── ServiceConfigurationError (服务配置错误)

└── java.lang.Exception (异常 - 可处理的程序问题)

├── 运行时异常 (RuntimeException - 未检查异常)
│ │
│ ├── 类型转换异常
│ │ └── ClassCastException (类型转换异常)
│ │
│ ├── 空指针异常
│ │ └── NullPointerException (空指针异常)
│ │
│ ├── 数组相关异常
│ │ ├── ArrayIndexOutOfBoundsException (数组越界)
│ │ ├── NegativeArraySizeException (负数组大小)
│ │ └── ArrayStoreException (数组存储类型不匹配)
│ │
│ ├── 字符串相关异常
│ │ ├── StringIndexOutOfBoundsException (字符串越界)
│ │ └── IndexOutOfBoundsException (索引越界)
│ │
│ ├── 数字相关异常
│ │ ├── ArithmeticException (算术异常如除零)
│ │ ├── NumberFormatException (数字格式异常)
│ │ └── IllegalArgumentException (非法参数)
│ │ ├── IllegalThreadStateException (非法线程状态)
│ │ ├── NumberFormatException
│ │ └── PatternSyntaxException (正则表达式语法)
│ │
│ ├── 安全相关异常
│ │ ├── SecurityException (安全异常)
│ │ └── AccessControlException (访问控制异常)
│ │
│ ├── 状态相关异常
│ │ ├── IllegalStateException (非法状态)
│ │ ├── UnsupportedOperationException (不支持的操作)
│ │ └── ConcurrentModificationException (并发修改异常)
│ │
│ ├── 枚举相关异常
│ │ └── EnumConstantNotPresentException (枚举常量不存在)
│ │
│ └── 其他运行时异常
│ ├── MissingResourceException (资源缺失)
│ └── EmptyStackException (空栈异常)

└── 检查异常 (Checked Exception - 必须处理)

├── I/O 异常
│ ├── IOException (I/O 异常基类)
│ │ ├── FileNotFoundException (文件未找到)
│ │ ├── EOFException (文件结束异常)
│ │ ├── CharConversionException (字符转换异常)
│ │ ├── ObjectStreamException (对象流异常)
│ │ │ ├── InvalidClassException (无效类)
│ │ │ ├── NotSerializableException (不可序列化)
│ │ │ └── WriteAbortedException (写入中断)
│ │ ├── InterruptedIOException (I/O 中断)
│ │ │ └── SocketTimeoutException (Socket 超时)
│ │ ├── UnsupportedEncodingException (不支持的编码)
│ │ ├── UTFDataFormatException (UTF 格式错误)
│ │ └── MalformedURLException (URL 格式错误)
│ │
│ └── SyncFailedException (同步失败)

├── SQL 异常
│ └── SQLException (SQL 异常)
│ ├── SQLWarning (SQL 警告)
│ ├── SQLDataException (SQL 数据异常)
│ ├── SQLIntegrityConstraintViolationException (完整性约束)
│ ├── SQLSyntaxErrorException (SQL 语法错误)
│ └── SQLTimeoutException (SQL 超时)

├── 类加载异常
│ ├── ClassNotFoundException (类未找到)
│ └── InstantiationException (实例化异常)

├── 反射异常
│ ├── IllegalAccessException (非法访问)
│ ├── NoSuchFieldException (字段不存在)
│ ├── NoSuchMethodException (方法不存在)
│ └── InvocationTargetException (调用目标异常)

├── 线程相关异常
│ └── InterruptedException (中断异常)

├── 图形界面异常
│ ├── AWTException (AWT 异常)
│ └── HeadlessException (无头模式异常)

├── 解析异常
│ ├── ParseException (解析异常)
│ ├── java.text.ParseException (文本解析异常)
│ └── java.util.prefs.InvalidPreferencesFormatException

├── 安全异常
│ └── GeneralSecurityException (安全异常基类)
│ ├── NoSuchAlgorithmException (算法不存在)
│ ├── KeyManagementException (密钥管理异常)
│ ├── SignatureException (签名异常)
│ └── CertificateException (证书异常)

├── 其他检查异常
│ ├── CloneNotSupportedException (克隆不支持)
│ ├── javax.naming.NamingException (命名异常)
│ ├── javax.xml.parsers.ParserConfigurationException
│ ├── org.xml.sax.SAXException (XML 解析异常)
│ └── javax.management.JMException

└── 自定义异常
└── UserDefinedException (用户自定义异常)

异常区分

含义 特点 常见类型
Error 表示程序无法处理的严重问题通常由 JVM 系统引起 不推荐捕获一旦发生程序通常无法恢复只能尽力记录日志或优雅退出 OutOfMemoryError内存溢出JVM 内存耗尽
StackOverflowError栈溢出通常由无限递归导致
Exception 表示程序运行时可能发生的可以处理的意外情况 分为受检异常和非受检异常 IOException文件读写或网络通信异常
SQLException数据库操作异常
维度 受检异常 非受检异常
继承关系 继承自 Exception 继承自 RuntimeExceptionError
编译器要求 强制处理不捕获或声明编译直接报错 不强制处理编译器不管但运行时可能崩溃
产生原因 外部环境因素如文件被删了网络断了数据库挂了 代码逻辑错误如空指针数组越界除以零
处理策略 必须处理重试降级提示用户 应该通过修正代码逻辑来避免如判空
典型代表 IOException | SQLException NullPointerException | ArrayIndexOutOfBoundsException

自定义异常

在项目中通常会定义自己的异常类继承自 RuntimeException这样可以携带业务错误码和提示信息方便前后端交互

1
2
3
4
5
6
7
8
public class BizException extends RuntimeException {
private int code;

public BizException(int code, String message) {
super(message);
this.code = code;
}
}

异常处理

⭐⭐ try-catch-finally

  • try包裹可能出错的代码
  • catch捕获并处理特定类型的异常注意顺序要从具体到宽泛子类在前父类在后
  • finally无论是否出错都会执行的代码通常用于释放资源如关闭文件流数据库连接
1
2
3
4
5
6
7
8
9
10
11
try {
// 风险代码
int a = 10 / 0;
} catch (ArithmeticException e) { // 捕获具体异常
System.err.println("算术异常: " + e.getMessage());
} catch (Exception e) { // 兜底捕获
e.printStackTrace();
} finally {
// 常用于释放资源
System.out.println("清理工作");
}

⭐⭐ throwthrows

  • throw写在方法体内用来抛出一个具体的异常对象
  • throws写在方法签名后面用来声明该方法可能会抛出某种异常让调用者处理
1
2
3
4
5
6
7
// 声明可能抛出 IOException
public void readFile(String path) throws IOException {
if (path == null) {
// 抛出一个具体的异常对象
throw new IOException("文件路径不能为空");
}
}

⭐⭐ try-with-resources

这是处理资源连接的最佳实践它能自动关闭实现了 AutoCloseable 接口的资源无需手动写 finally 块来关闭代码更简洁且不易出错

1
2
3
4
5
6
7
// 资源定义在 try() 括号里系统自动调用 close()
try (FileInputStream fis = new FileInputStream("test.txt")) {
int data = fis.read();
} catch (IOException e) {
e.printStackTrace();
}
// 不需要 fis.close()

大数

在 Java 中处理大数超出基本数据类型 intlongdouble 等范围的数值主要依赖 java.math 包中的两个核心类BigInteger 整数BigDecimal 浮点数

BigInteger

BigInteger 类是专门用于处理任意精度的整数当计算结果超出了基本数据类型如 long最大值约为 9×10^18的范围时可以使用该类

理论上 BigInteger 只受限于计算机内存大小可以表示极大或极小的整数例如几百位的质数

对象一旦创建其值就不能改变所有的运算方法都不会修改原对象而是返回一个新的 BigInteger 对象

⭐⭐ 初始化对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 通过字符串初始化 (最常用)
BigInteger a = new BigInteger("123456789012345678901234567890");
BigInteger b = new BigInteger("-98765432109876543210");

// 通过基本数据类型转换
BigInteger c = BigInteger.valueOf(100L);
BigInteger d = BigInteger.valueOf(Integer.MAX_VALUE).add(BigInteger.ONE);

// 从字节数组构建
byte[] bytes = {1, 2, 3, 4};
BigInteger e = new BigInteger(bytes);

// 生成一个 128 位的随机正整数
BigInteger randomNum = new BigInteger(128, new java.util.Random());

⭐⭐ 常用运算方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// --- 初始化 ---
// ⚠️ 重要使用字符串构造避免基本类型溢出
BigInteger a = new BigInteger("123456789012345678901234567890");
BigInteger b = new BigInteger("98765432109876543210");

// 1. 加法 (add)
// 计算 a + b
BigInteger sum = a.add(b);
System.out.println("1. 加法 (a + b): " + sum);

// 2. 减法 (subtract)
// 计算 a - b
BigInteger diff = a.subtract(b);
System.out.println("2. 减法 (a - b): " + diff);

// 3. 乘法 (multiply)
// 计算 a * b
BigInteger product = a.multiply(b);
System.out.println("3. 乘法 (a * b): " + product);

// 4. 除法 (divide)
// 计算 a / b (整除直接舍去小数部分)
BigInteger quotient = a.divide(b);
System.out.println("4. 除法 (a / b): " + quotient);

// 5. 取余 (remainder)
// 计算 a % b
BigInteger remainder = a.remainder(b);
System.out.println("5. 取余 (a % b): " + remainder);

// 6. 商和余数 (divideAndRemainder)
// 一次性获取商和余数返回值为数组 [商, 余]
BigInteger[] divRem = a.divideAndRemainder(b);
System.out.println("6. 商和余: 商=" + divRem[0] + ", 余=" + divRem[1]);

// 7. 幂运算 (pow)
// 计算 b 的 3 次方 (注意指数必须是 int 类型)
BigInteger power = b.pow(3);
System.out.println("7. 幂运算 (b^3): " + power);

// 8. 模幂运算 (modPow)
// 计算 (b^3) % a常用于加密算法比先 pow 再 remainder 效率更高
BigInteger modPow = b.modPow(BigInteger.valueOf(3), a);
System.out.println("8. 模幂 ((b^3) % a): " + modPow);

// 9. 绝对值 (abs)
// 创建一个负数演示 abs
BigInteger negative = new BigInteger("-999999999999999999999");
BigInteger absVal = negative.abs();
System.out.println("9. 绝对值 (|-...|): " + absVal);

// 10. 最大公约数 (gcd)
// 计算 a 和 b 的最大公约数
BigInteger gcdVal = a.gcd(b);
System.out.println("10. 最大公约数 (gcd): " + gcdVal);

// 11. 最小值 (min) 和 最大值 (max)
BigInteger minVal = a.min(b);
BigInteger maxVal = a.max(b);
System.out.println("11. 最小值 (min): " + minVal);
System.out.println(" 最大值 (max): " + maxVal);

// 12. 比较大小 (compareTo)
// 返回值: 1 (大于), 0 (等于), -1 (小于)
int comparison = a.compareTo(b);
String resultStr = (comparison > 0) ? "a > b" : (comparison < 0) ? "a < b" : "a == b";
System.out.println("12. 比较大小 (compareTo): " + resultStr + " (返回值:" + comparison + ")");

// 13. 位运算示例 (shiftLeft)
// 左移 2 位相当于乘以 2^2 (即乘以 4)
BigInteger shifted = b.shiftLeft(2);
System.out.println("13. 位左移 (b << 2): " + shifted);

// 14. 素数判定 (isProbablePrime)
// 判断 a 是否可能是素数 (参数 20 是置信度越高越准确但越慢)
boolean isPrime = a.isProbablePrime(20);
System.out.println("14. 素数判定 (a is prime?): " + isPrime);

// 15. 随机大数生成
// 生成一个 64 位的随机正整数
BigInteger randomBig = new BigInteger(64, new Random());
System.out.println("15. 随机大数 (64位): " + randomBig);

// 16. 类型转换与安全检查
// 尝试转换为 long如果超出范围会抛出 ArithmeticException
int i = a.intValue(); // 可能丢失精度
long l = a.longValue(); // 可能丢失精度
double d = a.doubleValue(); // 可能丢失精度
String s = a.toString(); // 转为字符串 (最安全)
try {
long safeLong = a.longValueExact();
System.out.println("16. 安全转换: " + safeLong);
} catch (ArithmeticException e) {
System.out.println("16. 安全转换失败: 数值超出 long 范围 (" + e.getMessage() + ")");
}

// 17. 位运算
// 支持 and, or, xor, not, shiftLeft, shiftRight 等位操作
BigInteger shifted = a.shiftLeft(2); // 相当于 a * 4

BigDecimal

BigDecimal 类是专门用于高精度浮点数计算的类它是金融商业计算中处理货币金额的标准解决方案

在计算机中floatdouble 类型是基于二进制浮点数标准IEEE 754存储的这导致许多十进制小数无法被精确表示

BigDecimal 通过存储任意精度的整数非标度值Unscaled Value和32位的整数标度Scale来精确表示十进制数

例如3.14 存储为 非标度值 314标度 2

⭐⭐ 初始化对象

1
2
3
4
5
6
7
8
9
10
11
12
// 通过字符串初始化 (最常用)
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("-0.2");

// 通过基本数据类型转换
BigDecimal c = BigDecimal.valueOf(0.1);

// 不能直接使用 double 参数的构造器
BigDecimal bad = new BigDecimal(0.1);
System.out.println(bad);
// 输出: 0.1000000000000000055511151231257827021181583404541015625
// 0.1 在进入构造器之前作为 double 字面量已经发生了精度丢失

⭐⭐ 舍入模式

枚举常量 别名 (旧版) 描述 逻辑 示例
UP ROUND_UP 远离零方向舍入 只要丢弃的部分非零就向绝对值增大的方向进位 1.1 → 2
-1.1 → -2
1.0 → 1
DOWN ROUND_DOWN 向零方向舍入 直接截断丢弃部分永不进位 1.9 → 1
-1.9 → -1
1.0 → 1
CEILING ROUND_CEILING 向正无穷方向舍入 向数轴右侧更大值取整正数同 `UP`负数同 `DOWN` 1.1 → 2
-1.1 → -1
1.0 → 1
FLOOR ROUND_FLOOR 向负无穷方向舍入 向数轴左侧更小值取整正数同 `DOWN`负数同 `UP` 1.1 → 1
-1.1 → -2
1.0 → 1
HALF_UP ROUND_HALF_UP 四舍五入 丢弃部分 > 0.5 则进位= 0.5 则进位 1.5 → 2
1.4 → 1
-1.5 → -2
HALF_DOWN ROUND_HALF_DOWN 五舍六入 丢弃部分 > 0.5 则进位= 0.5 则舍去 1.5 → 1
1.6 → 2
-1.5 → -1
HALF_EVEN ROUND_HALF_EVEN 银行家舍入法 丢弃部分 > 0.5 进位= 0.5 时看前一位奇数进位偶数舍去 1.5 → 2
2.5 → 2
UNNECESSARY ROUND_UNNECESSARY 禁止舍入 如果运算结果需要舍入即除不尽或精度不够直接抛出 ArithmeticException 1.5 → 抛异常
1.0 → 1

⭐⭐ 常用运算方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// 写在前边涉及金钱必用 BigDecimal构造要用字符串比较要用 compareTo除法必须定精度

// --- 初始化 ---
// ⚠️ 重要使用字符串构造避免 double 精度丢失
BigDecimal a = new BigDecimal("10.55");
BigDecimal b = new BigDecimal("3.2");
BigDecimal c = new BigDecimal("10.550");

// 1. 加法 (add)
// 计算 a + b
BigDecimal sum = a.add(b);
System.out.println("1. 加法 (a + b): " + sum);

// 2. 减法 (subtract)
// 计算 a - b
BigDecimal diff = a.subtract(b);
System.out.println("2. 减法 (a - b): " + diff);

// 3. 乘法 (multiply)
// 计算 a * b
BigDecimal product = a.multiply(b);
System.out.println("3. 乘法 (a * b): " + product);

// 4. 除法 (divide)
// 场景 A: 能整除的情况 (可以直接除)
BigDecimal x = new BigDecimal("10.0");
BigDecimal y = new BigDecimal("2.0");
BigDecimal divExact = x.divide(y);
System.out.println("4A. 整除 (10.0 / 2.0): " + divExact);

// 场景 B: 不能整除的情况 (必须指定精度和舍入模式否则抛异常)
// 计算 10 / 3保留 4 位小数使用四舍五入 (HALF_UP)
BigDecimal ten = new BigDecimal("10");
BigDecimal three = new BigDecimal("3");
BigDecimal divRounded = ten.divide(three, 4, RoundingMode.HALF_UP);
System.out.println("4B. 非整除 (10 / 3, 保留4位, 四舍五入): " + divRounded);

// 5. 取余 (remainder)
// 计算 a % b
BigDecimal rem = a.remainder(b);
System.out.println("5. 取余 (a % b): " + rem);

// 6. 绝对值 (abs)
BigDecimal negative = new BigDecimal("-99.88");
BigDecimal absVal = negative.abs();
System.out.println("6. 绝对值 (|-99.88|): " + absVal);

// 7. 设置精度/标度 (setScale)
// 将结果强制保留 2 位小数四舍五入
// 常用于货币计算确保最终显示格式统一
BigDecimal rawResult = new BigDecimal("12.34567");
BigDecimal scaled = rawResult.setScale(2, RoundingMode.HALF_UP);
System.out.println("7. 设置精度 (12.34567 -> 2位): " + scaled);

// 8. 最大值 (max) 和 最小值 (min)
BigDecimal maxVal = a.max(b);
BigDecimal minVal = a.min(b);
System.out.println("8. 最大值 (max): " + maxVal);
System.out.println(" 最小值 (min): " + minVal);

// 9. 比较大小 (compareTo)
// 返回值: 1 (大于), 0 (等于), -1 (小于)
// 比较数值大小忽略标度 (即 10.55 和 10.550 视为相等)
int cmp1 = a.compareTo(c);
System.out.println("9. 比较大小 (10.55 vs 10.550): " + cmp1);

// 10. 相等性判断 (equals)
// 比较数值 AND 标度 (即 10.55 和 10.550 视为不相等)
boolean eq1 = a.equals(c);
System.out.println("10. 对象相等 (equals 10.55 vs 10.550): " + eq1);

// 11. 移动小数点 (movePointLeft / movePointRight)
// 左移 2 位 (相当于除以 100)
BigDecimal shiftedLeft = a.movePointLeft(2);
System.out.println("11. 小数点左移2位 (10.55 -> 0.1055): " + shiftedLeft);

// 右移 1 位 (相当于乘以 10)
BigDecimal shiftedRight = a.movePointRight(1);
System.out.println(" 小数点右移1位 (10.55 -> 105.5): " + shiftedRight);

// 12. 幂运算 (pow)
// 计算 b 的 2 次方 (指数必须是 int)
BigDecimal power = b.pow(2);
System.out.println("12. 幂运算 (3.2^2): " + power);

// 13. 转换为基本类型 (带安全检查)
try {
// longValueExact() 如果超出 long 范围或包含小数部分会抛异常
long safeLong = a.longValueExact();
System.out.println("13. 安全转 long: " + safeLong);
} catch (ArithmeticException e) {
System.out.println("13. 安全转 long 失败: " + e.getMessage());
// 输出: Rounding necessary (因为 10.55 有小数部分无法精确转为 long)
}

// 如果想去掉小数部分再转可以先 setScale(0, RoundingMode.DOWN)
long truncatedLong = a.setScale(0, RoundingMode.DOWN).longValueExact();
System.out.println(" 去小数后转 long: " + truncatedLong); // 10

包装类

Java 中的包装类是将基本数据类型封装成对象的类Java 为每个基本数据类型都提供了对应的包装类

基本数据类型 相对应的包装类 使用示例
byte Byte Byte b = 10;
short Short Short s = 100;
int Integer Integer i = 100;
long Long Long l = 1000L;
float Float Float f = 3.14f;
double Double Double d = 3.14159;
char Character Character c = 'A';
boolean Boolean Boolean bool = true;

装箱与拆箱

⭐⭐ 自动装箱

自动将基本类型转换为包装类对象

1
2
3
4
5
// 编译前
Integer num = 100;

// 编译后实际执行
Integer num = Integer.valueOf(100);

⭐⭐ 自动拆箱

自动将包装类对象转换为基本类型

1
2
3
4
5
6
7
// 编译前
Integer num = 100;
int value = num;

// 编译后实际执行
Integer num = Integer.valueOf(100);
int value = num.intValue();

缓存机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Integer 缓存范围-128 到 127
Integer i1 = 100;
Integer i2 = 100;
System.out.println(i1 == i2); // true使用缓存对象

Integer i3 = 200;
Integer i4 = 200;
System.out.println(i3 == i4); // false创建新对象

// 使用 new 关键字创建不适用缓存
Integer i5 = new Integer(100);
Integer i6 = new Integer(100);
System.out.println(i5 == i6); // false



// Byte, Short, Long-128 到 127
Short s1 = 100;
Short s2 = 100;
System.out.println(s1 == s2); // true


// Character0 到 127
Character c1 = 'a';
Character c2 = 'a';
System.out.println(c1 == c2); // true


// Booleantrue 和 false 都有缓存
Boolean b1 = true;
Boolean b2 = true;
System.out.println(b1 == b2); // true

类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 字符串转基本类型
int i = Integer.parseInt("123");
double d = Double.parseDouble("3.14");
boolean b = Boolean.parseBoolean("true");

// 基本类型转字符串
String s1 = Integer.toString(100);
String s2 = String.valueOf(100);
String s3 = "" + 100;

// 进制转换
String bin = Integer.toBinaryString(10); // "1010"
String hex = Integer.toHexString(255); // "ff"
String oct = Integer.toOctalString(8); // "10"

比较方法

1
2
3
4
5
6
7
8
9
10
// compareTo 方法
Integer num1 = 100;
Integer num2 = 200;
int result = num1.compareTo(num2); // 负数-1

// equals 方法推荐用于比较对象值
Integer a = 1000;
Integer b = 1000;
System.out.println(a.equals(b)); // true
System.out.println(a == b); // false

数值操作

1
2
3
4
5
6
7
8
// 最大值最小值
int max = Integer.max(10, 20); // 20
int min = Integer.min(10, 20); // 10
int sum = Integer.sum(10, 20); // 30

// 常量
int maxValue = Integer.MAX_VALUE; // 2147483647
int minValue = Integer.MIN_VALUE; // -2147483648

字符串

String 类用于保存字符串也就是一组字符序列

类的特性

⭐⭐ 不可变性

String 类的对象一旦创建其内容就不能被改变

1
2
3
4
5
6
7
8
9
10
String str = "Hello";
str = str + " World"; // 实际上创建了新的 String 对象
System.out.println(str); // "Hello World"

// 原理分析
String s1 = "Java";
String s2 = s1;
s1 = s1 + " Programming";
System.out.println(s1); // "Java Programming"
System.out.println(s2); // "Java" - 原对象未改变

⭐⭐ 字符串常量池

JVM 维护一个字符串常量池用于存储字符串字面量实现字符串重用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 字面量创建 - 存储在常量池
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2); // true - 引用同一个对象

// 使用 new 创建 - 存储在堆中
String str3 = new String("Hello");
String str4 = new String("Hello");
System.out.println(str3 == str4); // false - 不同对象
System.out.println(str3.equals(str4)); // true - 内容相同

// intern() 方法 - 手动放入常量池
String str5 = new String("Hello").intern();
System.out.println(str1 == str5); // true

创建方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 方式1字面量推荐
String s1 = "Hello";
String s2 = "Hello"; // 复用常量池中的对象

// 方式2使用 new 关键字
String s3 = new String("Hello"); // 创建两个对象堆中 + 常量池

// 方式3字符数组
char[] chars = {'H', 'e', 'l', 'l', 'o'};
String s4 = new String(chars);

// 方式4字节数组
byte[] bytes = {72, 101, 108, 108, 111};
String s5 = new String(bytes);

去除空白

1
2
3
4
5
6
7
8
9
10
String str = "  Hello World  \n";

// trim() - 去除首尾空白空格制表符等
String trimmed = str.trim(); // "Hello World"

// strip() - Java 11+支持 Unicode 空白字符
String stripped = str.strip(); // "Hello World"

// stripLeading() - 去除前导空白
// stripTrailing() - 去除尾部空白

字符串比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String s1 = "Hello";
String s2 = "hello";

// equals() - 区分大小写比较
System.out.println(s1.equals(s2)); // false

// equalsIgnoreCase() - 忽略大小写比较
System.out.println(s1.equalsIgnoreCase(s2)); // true

// compareTo() - 字典序比较
System.out.println(s1.compareTo(s2)); // 负数"H" < "h" (ASCII码差值)

// compareToIgnoreCase() - 忽略大小写字典序比较
System.out.println(s1.compareToIgnoreCase(s2)); // 0

查找与判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
String str = "Hello World, Hello Java";

// 判断是否包含子串
boolean contains = str.contains("World"); // true

// 查找索引
int index1 = str.indexOf('o'); // 4 - 第一次出现
int index2 = str.lastIndexOf('o'); // 16 - 最后一次出现
int index3 = str.indexOf("Hello", 1); // 12 - 从指定位置开始查找

// 判断开头结尾
boolean startsWith = str.startsWith("Hello"); // true
boolean endsWith = str.endsWith("Java"); // true

// 判空
String empty = "";
String nullStr = null;
boolean isEmpty = empty.isEmpty(); // true
boolean isBlank = " ".isBlank(); // true (Java 11+)

截取与分割

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String str = "Hello World";

// substring() - 截取子串
String sub1 = str.substring(6); // "World"
String sub2 = str.substring(0, 5); // "Hello" - [0,5)

// split() - 分割字符串
String csv = "apple,banana,orange";
String[] fruits = csv.split(","); // ["apple", "banana", "orange"]

// 正则表达式分割
String text = "Java|Python|C++";
String[] langs = text.split("\\|"); // 需要转义

// 限制分割次数
String[] parts = "a,b,c,d".split(",", 2); // ["a", "b,c,d"]

替换与转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
String str = "Hello World";

// replace() - 字符/字符串替换
String replaced1 = str.replace('o', 'a'); // "Hella Warld"
String replaced2 = str.replace("World", "Java"); // "Hello Java"

// replaceAll() - 正则表达式替换
String numbers = "a1b2c3";
String result = numbers.replaceAll("\\d", "*"); // "a*b*c*"

// replaceFirst() - 替换第一个匹配
String text = "apple apple apple";
String newText = text.replaceFirst("apple", "orange"); // "orange apple apple"

// 大小写转换
String upper = str.toUpperCase(); // "HELLO WORLD"
String lower = str.toLowerCase(); // "hello world"

拼接与格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// concat() - 拼接字符串
String s1 = "Hello".concat(" World"); // "Hello World"

// join() - 使用分隔符连接
String joined = String.join("-", "2023", "01", "15"); // "2023-01-15"
List<String> list = Arrays.asList("Java", "Python", "C++");
String result = String.join(", ", list); // "Java, Python, C++"

// format() - 格式化字符串
String name = "Alice";
int age = 25;
String formatted = String.format("Name: %s, Age: %d", name, age);
// "Name: Alice, Age: 25"

// 格式化数字
String price = String.format("%.2f", 19.999); // "20.00"

可变字符串

频繁修改字符串时使用可变字符串类

  • StringBuilder非线程安全性能高
  • StringBuffer线程安全性能稍低
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
StringBuilder sb = new StringBuilder("Hello");

// 追加
sb.append(" World"); // "Hello World"
sb.append(123); // "Hello World123"

// 插入
sb.insert(5, " Java"); // "Hello Java World123"

// 删除
sb.delete(5, 10); // "Hello World123"
sb.deleteCharAt(5); // "Hell World123"

// 替换
sb.replace(5, 10, "Java"); // "HelloJava123"

// 反转
sb.reverse(); // "321avaJolleH"

// 容量相关
int capacity = sb.capacity(); // 当前容量
sb.ensureCapacity(100); // 确保容量
sb.setLength(10); // 设置长度

集合类

Java 集合框架是 Java 语言中用于存储和操作一组对象的核心工具它提供了一套统一高效且易于使用的数据结构和算法整个 Java 集合框架主要由两大顶级接口构成Collection单列集合Map双列集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
java.lang.Object

├── Collection 体系 (单列集合)
│ │
│ └── java.util.Collection (集合根接口)
│ │
│ ├── List (有序可重复有索引)
│ │ │
│ │ ├── ArrayList (数组实现随机访问快增删慢)
│ │ ├── LinkedList (双向链表实现增删快随机访问慢)
│ │ ├── Vector (线程安全的动态数组已过时)
│ │ │ └── Stack (栈继承自Vector)
│ │ └── CopyOnWriteArrayList (并发包写时复制)
│ │
│ ├── Set (无序不可重复)
│ │ │
│ │ ├── HashSet (哈希表实现无序)
│ │ │ └── LinkedHashSet (哈希表+链表有序)
│ │ │
│ │ ├── TreeSet (红黑树实现可排序)
│ │ │
│ │ ├── EnumSet (枚举专用位向量实现)
│ │ │
│ │ └── CopyOnWriteArraySet (并发包写时复制)
│ │
│ ├── Queue (队列)
│ │ │
│ │ ├── PriorityQueue (优先队列堆实现)
│ │ │
│ │ ├── ArrayDeque (双端队列数组实现)
│ │ │
│ │ ├── LinkedList (也实现了Deque)
│ │ │
│ │ └── 并发队列
│ │ ├── ConcurrentLinkedQueue (非阻塞队列)
│ │ └── BlockingQueue (阻塞队列接口)
│ │ ├── ArrayBlockingQueue (有界数组队列)
│ │ ├── LinkedBlockingQueue (链表队列)
│ │ ├── PriorityBlockingQueue (优先阻塞队列)
│ │ ├── DelayQueue (延迟队列)
│ │ ├── SynchronousQueue (同步队列)
│ │ └── LinkedTransferQueue (传输队列)
│ │
│ └── 遗留集合类
│ ├── Vector (JDK 1.0)
│ ├── Stack (JDK 1.0)
│ └── Hashtable (JDK 1.0)

├── Map 体系 (双列集合)
│ │
│ └── java.util.Map (映射根接口)
│ │
│ ├── 哈希表实现
│ │ ├── HashMap (哈希表无序允许null键值)
│ │ │ └── LinkedHashMap (哈希表+链表有序)
│ │ │
│ │ ├── Hashtable (线程安全不允许null已过时)
│ │ │
│ │ └── ConcurrentHashMap (并发包分段锁/CAS)
│ │
│ ├── 树实现
│ │ └── TreeMap (红黑树可排序)
│ │
│ ├── 专用Map
│ │ ├── EnumMap (枚举专用)
│ │ ├── WeakHashMap (弱引用键)
│ │ ├── IdentityHashMap (引用相等)
│ │ └── Properties (属性文件)
│ │
│ └── 遗留Map
│ └── Dictionary (抽象类已废弃)
│ └── Hashtable

└── 辅助工具类

├── java.util.Collections (集合工具类)
├── java.util.Arrays (数组工具类)
├── java.util.Objects (对象工具类)

└── 迭代器
├── Iterator (迭代器接口)
│ └── ListIterator (双向迭代器)
└── Enumeration (遗留枚举接口)

List

List 是一种有序可重复的集合元素按照插入顺序排列并且可以通过整数索引下标来精确访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 创建 ArrayList
List<String> list = new ArrayList<>();

// --- 增 (Add) ---
list.add("Apple"); // 尾部添加
list.add("Banana");
list.add(1, "Orange"); // 指定索引插入: [Apple, Orange, Banana]
list.addAll(Arrays.asList("Grape", "Melon")); // 批量添加

// --- 查 (Get/Access) ---
String fruit = list.get(0); // 获取索引0的元素: "Apple"
int index = list.indexOf("Banana"); // 获取元素索引: 2
boolean hasMelon = list.contains("Melon"); // 判断包含: true

// --- 改 (Set/Update) ---
list.set(1, "Mango"); // 替换索引1的元素: [Apple, Mango, Banana...]

// --- 删 (Remove) ---
list.remove(2); // 删除索引2的元素 ("Banana")
list.remove("Grape"); // 删除第一个匹配的对象 "Grape"

// --- 其他常用操作 ---
System.out.println("列表大小: " + list.size()); // 获取长度
List<String> subList = list.subList(0, 2); // 截取子列表 [0, 2)

// 遍历方式
System.out.println("--- 遍历 ---");
for (int i = 0; i < list.size(); i++) {
System.out.println("索引:" + i + ", 值:" + list.get(i));
}
// 增强for循环
for (String item : list) {
System.out.println(item);
}

Set

Set 是一个不允许包含重复元素的集合其核心用途是数据去重元素的唯一性通过 hashCode() 和 equals() 方法来保证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 创建 HashSet (去重)
Set<Integer> set = new HashSet<>();

// --- 增 (Add) ---
set.add(10);
set.add(20);
set.add(10); // 重复添加失败返回 false
set.addAll(Arrays.asList(30, 40, 50));

System.out.println("Set内容: " + set); // 顺序不保证如 [10, 20, 30, 40, 50]

// --- 查 (Contains) ---
boolean exists = set.contains(20); // true
// Set 没有 get(index) 方法必须遍历

// --- 删 (Remove) ---
set.remove(30);
set.clear(); // 清空

// --- 特有场景演示 ---

// 1. LinkedHashSet: 保持插入顺序
Set<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("C");
linkedSet.add("A");
linkedSet.add("B");
System.out.println("LinkedHashSet顺序: " + linkedSet); // 输出: [C, A, B]

// 2. TreeSet: 自动排序
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(5);
treeSet.add(1);
treeSet.add(9);
System.out.println("TreeSet排序: " + treeSet); // 输出: [1, 5, 9]

// 交集操作
Set<Integer> s1 = new HashSet<>(Arrays.asList(1, 2, 3));
Set<Integer> s2 = new HashSet<>(Arrays.asList(2, 3, 4));
s1.retainAll(s2); // 保留交集: [2, 3]
System.out.println("交集: " + s1);

Queue

Queue 是一种特殊的线性表通常遵循先进先出FIFO的原则它主要用于在数据处理前暂存元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 1. 普通队列 (FIFO) - 使用 ArrayDeque 推荐
Queue<String> queue = new ArrayDeque<>();

// --- 入队 (Enqueue) ---
queue.offer("Task 1"); // 推荐失败返回 false
queue.offer("Task 2");
// queue.add("Task 3"); // 不推荐容量受限可能抛异常

// --- 查头 (Peek) ---
String head = queue.peek(); // 获取但不删除队头: "Task 1"
System.out.println("队头元素: " + head);

// --- 出队 (Dequeue) ---
String task = queue.poll(); // 获取并删除队头: "Task 1"
System.out.println("处理任务: " + task);
System.out.println("剩余队列: " + queue); // [Task 2]

// 2. 优先级队列 (PriorityQueue)
System.out.println("\n--- 优先级队列 ---");
// 默认小顶堆数字小的优先级高
Queue<Integer> pQueue = new PriorityQueue<>();
pQueue.offer(10);
pQueue.offer(5);
pQueue.offer(20);

while (!pQueue.isEmpty()) {
System.out.print(pQueue.poll() + " "); // 输出: 5 10 20 (自动排序)
}

// 3. 双端队列作栈 (LIFO)
System.out.println("\n--- 双端队列作栈 ---");
Deque<String> stack = new ArrayDeque<>();
stack.push("A"); // 压栈
stack.push("B");
System.out.println("弹栈: " + stack.pop()); // 输出: B

Map

Map 用于存储键值对映射其中键Key是唯一的但值Value可以重复一个键最多只能映射一个值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 创建 HashMap
Map<String, Integer> map = new HashMap<>();

// --- 增/改 (Put) ---
map.put("Apple", 10);
map.put("Banana", 20);
map.put("Apple", 15); // Key已存在覆盖值返回旧值 10

// putIfAbsent: 仅当Key不存在时放入 (原子操作)
map.putIfAbsent("Orange", 30);

// --- 查 (Get) ---
Integer count = map.get("Banana"); // 20
// getOrDefault: 避免空指针非常实用
Integer unknown = map.getOrDefault("Grape", 0); // 0

// --- 判断 ---
boolean hasKey = map.containsKey("Apple"); // true
boolean hasValue = map.containsValue(20); // false (因为Apple被改为15了)

// --- 删 (Remove) ---
map.remove("Apple");
// 条件删除: 只有当 Key="Banana" 且 Value=20 时才删除
map.remove("Banana", 20);

// --- 遍历 (三种高效方式) ---
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);

System.out.println("--- 遍历 KeySet ---");
for (String key : map.keySet()) {
System.out.println(key + " = " + map.get(key));
}

System.out.println("--- 遍历 EntrySet (推荐最高效) ---");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}

System.out.println("--- Java 8 forEach ---");
map.forEach((k, v) -> System.out.println(k + " -> " + v));

// --- 特殊实现TreeMap 排序 ---
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("C", 3);
treeMap.put("A", 1);
treeMap.put("B", 2);
System.out.println("TreeMap顺序: " + treeMap); // A=1, B=2, C=3

Collections

Collections 是 Java 集合框架中的一个核心工具类它包含了一系列操作集合如 ListSetMap 等的静态方法

⭐⭐ 排序与顺序操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 对列表进行自然排序元素需实现 Comparable 接口
List<Integer> numbers = new ArrayList<>(Arrays.asList(5, 2, 9, 1, 5, 6));
Collections.sort(numbers);
System.out.println("排序后: " + numbers);
// 输出: [1, 2, 5, 5, 6, 9]


// 随机打乱列表元素的顺序
List<String> cards = new ArrayList<>(Arrays.asList("A", "K", "Q", "J"));
Collections.shuffle(cards);
System.out.println("洗牌后: " + cards);
// 输出示例: [Q, A, J, K] (顺序随机)


// 反转列表中元素的顺序
List<String> list = new ArrayList<>(Arrays.asList("One", "Two", "Three"));
Collections.reverse(list);
System.out.println("反转后: " + list);
// 输出: [Three, Two, One]


// 交换列表中指定位置的元素
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
Collections.swap(list, 0, 3); // 交换第一个和最后一个
System.out.println("交换后: " + list);
// 输出: [D, B, C, A]


// 用指定元素替换列表中所有元素
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Collections.fill(list, "X");
System.out.println("填充后: " + list);
// 输出: [X, X, X]


// 将列表元素旋转指定距离正数向右负数向左
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
Collections.rotate(list, 2); // 向右旋转2位
System.out.println("旋转后: " + list);
// 输出: [4, 5, 1, 2, 3]

⭐⭐ 搜索与查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用二分查找法在已排序的列表中查找元素
List<Integer> list = new ArrayList<>(Arrays.asList(1, 3, 5, 7, 9));
// 注意列表必须先排序
int index = Collections.binarySearch(list, 5);
System.out.println("索引位置: " + index);
// 输出: 2


// 返回源列表中首次出现指定子列表的起始位置
List<String> source = new ArrayList<>(Arrays.asList("A", "B", "C", "D", "E"));
List<String> target = new ArrayList<>(Arrays.asList("C", "D"));
int index = Collections.indexOfSubList(source, target);
System.out.println("子列表起始索引: " + index);
// 输出: 2

⭐⭐ 最值与统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 返回集合中的最大元素
List<Integer> numbers = Arrays.asList(10, 5, 20, 8);
Integer maxVal = Collections.max(numbers);
System.out.println("最大值: " + maxVal);
// 输出: 20


// 返回集合中的最小元素
List<Integer> numbers = Arrays.asList(10, 5, 20, 8);
Integer minVal = Collections.min(numbers);
System.out.println("最小值: " + minVal);
// 输出: 5


// 统计指定元素在集合中出现的次数
List<String> list = Arrays.asList("A", "B", "A", "C", "A");
int count = Collections.frequency(list, "A");
System.out.println("'A' 出现的次数: " + count);
// 输出: 3


// 判断两个集合是否没有交集即是否互斥
List<String> list1 = Arrays.asList("A", "B");
List<String> list2 = Arrays.asList("C", "D");
boolean isDisjoint = Collections.disjoint(list1, list2);
System.out.println("是否无交集: " + isDisjoint);
// 输出: true

⭐⭐ 不可变集合包装

1
2
3
4
5
6
7
8
9
10
11
// 返回一个只读视图尝试修改会抛出 UnsupportedOperationException
List<String> original = new ArrayList<>(Arrays.asList("A", "B"));
List<String> readOnly = Collections.unmodifiableList(original);

try {
readOnly.add("C"); // 这会抛出异常
} catch (UnsupportedOperationException e) {
System.out.println("无法修改只读列表: " + e.getMessage());
}

// unmodifiableSet, unmodifiableMap 等用法类似

⭐⭐ 同步集合包装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 1. 创建同步列表
List<Integer> safeList = Collections.synchronizedList(new ArrayList<>());

int threadCount = 10;
CountDownLatch latch = new CountDownLatch(threadCount);

// 2. 启动多个线程同时写入
for (int i = 0; i < threadCount; i++) {
final int taskId = i;
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
safeList.add(taskId * 1000 + j);
}
latch.countDown();
}).start();
}

latch.await(); // 等待所有线程结束
System.out.println("最终列表大小: " + safeList.size());
// 预期结果10 * 1000 = 10000 (如果没有数据丢失)

// 3. 安全地读取/遍历
System.out.println("开始遍历...");
synchronized (safeList) { // 关键遍历时必须手动锁住
int count = 0;
for (Integer val : safeList) {
count++;
// 模拟处理耗时增加并发冲突概率
if (count % 5000 == 0) System.out.println("处理中...");
}
System.out.println("遍历完成共处理: " + count + " 个元素");
}


// synchronizedMap, synchronizedSet 等用法类似

⭐⭐ 空集合与单元素集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 返回一个空的不可变的列表
List<String> empty = Collections.emptyList();
System.out.println("空列表大小: " + empty.size());
// 输出: 0
// empty.add("A"); // 会抛出 UnsupportedOperationException


// 返回一个只包含指定对象的不可变集合
Set<String> singleSet = Collections.singleton("OnlyOne");
System.out.println("单元素集合: " + singleSet);
// 输出: [OnlyOne]


// 返回一个只包含指定键值对的不可变映射
Map<String, Integer> singleMap = Collections.singletonMap("Key", 100);
System.out.println("单元素Map: " + singleMap);
// 输出: {Key=100}

⭐⭐ 其他方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 将可变参数列表中的所有元素添加到指定集合中
List<String> list = new ArrayList<>();
Collections.addAll(list, "A", "B", "C");
System.out.println("添加后: " + list);
// 输出: [A, B, C]


// 返回一个动态类型安全的列表视图在运行时检查类型
List rawList = new ArrayList();
// 创建一个只能存放 String 的检查视图
List<String> checkedList = Collections.checkedList(rawList, String.class);
checkedList.add("Safe String");
// checkedList.add(123); // 如果在运行时尝试放入整数会抛出 ClassCastException

正则验证

Java 中正则相关的主要类位于 java.util.regex 包中Pattern 编译后的正则表达式Matcher 匹配器用于执行匹配操作

正则字符

类别 表达式 匹配数据
基本字符类 [abc] abc
[^abc] 除 abc 外的任意字符
[a-zA-Z] 任意大小写字母
[0-9] 任意数字
[a-d[m-p]] a-d或m-p并集
[a-z&&[def]] de或f交集
预定义字符类 . 任意字符
\d 数字[0-9]
\D 非数字[^0-9]
\s 空白字符
\S 非空白字符
\w 单词字符[a-zA-Z_0-9]
\W 非单词字符
边界匹配器 ^ 行的开头
$ 行的结尾
\b 单词边界
\B 非单词边界
\A 输入的开头
\z 输入的结尾
\Z 除最后终止符外的结尾
量词 X? X出现0次或1次
X* X出现0次或多次
X+ X出现1次或多次
X{n} X恰好出现n次
X{n,} X至少出现n次
X{n,m} X出现n到m次

Pattern

1
2
3
4
5
6
7
8
// 编译正则表达式
Pattern pattern = Pattern.compile("\\d+");

// 常用方法
pattern.pattern(); // 返回正则表达式字符串
pattern.flags(); // 返回匹配标志
pattern.split("a1b2c3"); // 分割字符串
pattern.matcher("input"); // 创建Matcher对象

Matcher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher("abc123def456");

// 匹配方法
matcher.matches(); // 完全匹配
matcher.lookingAt(); // 从开头匹配
matcher.find(); // 查找下一个匹配
matcher.find(int start); // 从指定位置开始查找

// 获取匹配结果
if (matcher.find()) {
matcher.start(); // 起始索引
matcher.end(); // 结束索引+1
matcher.group(); // 匹配的字符串
matcher.groupCount(); // 分组数量
}

// 替换方法
matcher.replaceAll("replacement");
matcher.replaceFirst("replacement");
matcher.appendReplacement(StringBuffer sb, String replacement);
matcher.appendTail(StringBuffer sb);

基本匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class RegexDemo {
public static void main(String[] args) {
// 验证邮箱
String email = "user@example.com";
String emailRegex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
System.out.println(email.matches(emailRegex));

// 验证手机号简单示例
String phone = "13812345678";
String phoneRegex = "^1[3-9]\\d{9}$";
System.out.println(phone.matches(phoneRegex));

// 提取所有数字
String text = "价格100元折扣20%";
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println("找到数字" + matcher.group());
}
}
}

分组捕获

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class GroupDemo {
public static void main(String[] args) {
String text = "姓名张三年龄25电话13812345678";
Pattern pattern = Pattern.compile("姓名(\\S+)年龄(\\d+)电话(1[3-9]\\d{9})");
Matcher matcher = pattern.matcher(text);

if (matcher.find()) {
System.out.println("完整匹配" + matcher.group(0));
System.out.println("姓名" + matcher.group(1));
System.out.println("年龄" + matcher.group(2));
System.out.println("电话" + matcher.group(3));
}
}
}

替换操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ReplaceDemo {
public static void main(String[] args) {
// 简单替换
String text = "Hello 123 World 456";
String result = text.replaceAll("\\d+", "数字");
System.out.println(result); // Hello 数字 World 数字

// 使用分组替换
String date = "2024-01-15";
String newDate = date.replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$2/$3/$1");
System.out.println(newDate); // 01/15/2024

// 高级替换
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher(text);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
int num = Integer.parseInt(matcher.group());
matcher.appendReplacement(sb, String.valueOf(num * 2));
}
matcher.appendTail(sb);
System.out.println(sb.toString()); // Hello 246 World 912
}
}

匹配标志

1
2
3
4
5
6
7
8
9
10
// 常用标志
Pattern.CASE_INSENSITIVE // 忽略大小写等价于 (?i)
Pattern.MULTILINE // 多行模式等价于 (?m)
Pattern.DOTALL // .匹配所有字符包括换行等价于 (?s)
Pattern.UNICODE_CASE // Unicode大小写等价于 (?u)

// 使用示例
Pattern pattern = Pattern.compile("java", Pattern.CASE_INSENSITIVE);
// 或使用内嵌标志
Pattern pattern2 = Pattern.compile("(?i)java");

常用表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
String regex = 
// 邮箱
"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";

// 手机号
"^1[3-9]\\d{9}$";

// URL
"^(https?://)?([\\da-z.-]+)\\.([a-z.]{2,6})([/\\w .-]*)*/?$";

// IP地址
"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";

// 日期 YYYY-MM-DD
"^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$";

// 中文
"^[\\u4e00-\\u9fa5]+$";

// 邮政编码
"^[1-9]\\d{5}$";

数学运算

Math 类是 Java 提供的一个工具类位于 java.lang 包中包含执行基本数学运算的方法如指数对数平方根三角函数等

数学常量

1
2
3
4
5
// 圆周率 π
System.out.println(Math.PI); // 3.141592653589793

// 自然常数 e
System.out.println(Math.E); // 2.718281828459045

基本运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 绝对值
Math.abs(-10); // 10
Math.abs(-3.14); // 3.14

// 最大值/最小值
Math.max(10, 20); // 20
Math.min(10, 20); // 10

// 四舍五入
Math.round(3.4); // 3
Math.round(3.6); // 4
Math.round(3.5); // 4 (注意银行家舍入法实际是向正无穷舍入)

// 向上取整/向下取整
Math.ceil(3.1); // 4.0
Math.floor(3.9); // 3.0

// 取整直接去掉小数部分
Math.floorDiv(7, 2); // 3 (整除)
Math.floorMod(7, 2); // 1 (取余)

平方运算

1
2
3
4
5
6
7
8
9
10
11
12
13
// 幂运算
Math.pow(2, 3); // 8.0 (2的3次方)
Math.pow(4, 0.5); // 2.0 (4的平方根)

// 平方根
Math.sqrt(16); // 4.0
Math.sqrt(2); // 1.4142135623730951

// 立方根
Math.cbrt(27); // 3.0

// 开平方更精确
Math.hypot(3, 4); // 5.0 (计算 sqrt(x² + y²))

指数运算

1
2
3
4
5
6
7
8
9
10
11
// 自然指数 e^x
Math.exp(1); // 2.718281828459045 (e^1)

// 自然对数 ln(x)
Math.log(Math.E); // 1.0

// 常用对数 log10(x)
Math.log10(100); // 2.0

// 任意底数的对数
Math.log(8) / Math.log(2); // 3.0 (log₂8)

三角函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 角度转弧度
Math.toRadians(180); // 3.141592653589793 (π弧度)

// 弧度转角度
Math.toDegrees(Math.PI); // 180.0

// 正弦余弦正切参数为弧度
Math.sin(Math.PI / 2); // 1.0
Math.cos(0); // 1.0
Math.tan(Math.PI / 4); // 1.0

// 反正弦反余弦反正切
Math.asin(1); // 1.5707963267948966 (π/2)
Math.acos(0); // 1.5707963267948966
Math.atan(1); // 0.7853981633974483 (π/4)

// 直角坐标转极坐标角度
Math.atan2(1, 1); // 0.7853981633974483 (π/4)

随机数

1
2
3
4
5
6
7
8
9
// 生成 [0.0, 1.0) 的随机数
double random = Math.random(); // 0.0 <= random < 1.0

// 生成 [0, 100) 的随机整数
int randomInt = (int)(Math.random() * 100);

// 生成 [min, max] 的随机整数
int min = 10, max = 20;
int rangeRandom = min + (int)(Math.random() * (max - min + 1));

其他方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 符号函数
Math.signum(5.5); // 1.0
Math.signum(-3.2); // -1.0
Math.signum(0); // 0.0

// 判断符号是否相同
Math.copySign(5, -3); // -5.0 (将第二个数的符号赋给第一个数)

// 浮点数比较
Math.nextUp(1.0); // 大于1.0的最小浮点数
Math.nextDown(1.0); // 小于1.0的最大浮点数

// 精确运算JDK 8+
Math.addExact(10, 20); // 30
Math.subtractExact(100, 30); // 70
Math.multiplyExact(5, 6); // 30
Math.incrementExact(9); // 10
Math.decrementExact(10); // 9
Math.negateExact(5); // -5

数组操作

Arrays 类是 Java 提供的数组工具类位于 java.util 包中包含用于操作数组的各种静态方法如排序搜索比较填充转换为字符串等

数组转字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.Arrays;

// 一维数组
int[] arr1 = {1, 2, 3, 4, 5};
System.out.println(Arrays.toString(arr1));
// 输出: [1, 2, 3, 4, 5]

// 二维数组
int[][] arr2 = {{1, 2}, {3, 4}, {5, 6}};
System.out.println(Arrays.deepToString(arr2));
// 输出: [[1, 2], [3, 4], [5, 6]]

// 注意二维数组不能用toString
System.out.println(Arrays.toString(arr2));
// 输出: [[I@15db9742, [I@6d06d69c, [I@7852e922] (对象地址)

数组排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 基本类型排序升序
int[] numbers = {5, 2, 8, 1, 9};
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers));
// 输出: [1, 2, 5, 8, 9]

// 指定范围排序
int[] arr = {5, 2, 8, 1, 9, 3, 7};
Arrays.sort(arr, 1, 5); // 排序索引[1,5)的元素
System.out.println(Arrays.toString(arr));
// 输出: [5, 1, 2, 8, 9, 3, 7]

// 对象数组排序需实现Comparable
String[] names = {"Bob", "Alice", "Charlie"};
Arrays.sort(names);
System.out.println(Arrays.toString(names));
// 输出: [Alice, Bob, Charlie]

// 自定义排序使用Comparator
Integer[] nums = {5, 2, 8, 1, 9};
Arrays.sort(nums, (a, b) -> b - a); // 降序
System.out.println(Arrays.toString(nums));
// 输出: [9, 8, 5, 2, 1]

// 对象数组按指定字段排序
class Person {
String name;
int age;
Person(String name, int age) { this.name = name; this.age = age; }
public String toString() { return name + ":" + age; }
}

Person[] persons = {
new Person("Bob", 25),
new Person("Alice", 30),
new Person("Charlie", 20)
};

// 按年龄排序
Arrays.sort(persons, (p1, p2) -> p1.age - p2.age);
System.out.println(Arrays.toString(persons));
// 输出: [Charlie:20, Bob:25, Alice:30]

二分查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 前提数组必须已排序
int[] arr = {1, 3, 5, 7, 9, 11};

// 查找存在的元素
int index = Arrays.binarySearch(arr, 7);
System.out.println(index); // 输出: 3

// 查找不存在的元素
int notFound = Arrays.binarySearch(arr, 6);
System.out.println(notFound);
// 输出: -4 (插入点 = -索引-1即应该插入在索引3的位置)

// 指定范围查找
int rangeSearch = Arrays.binarySearch(arr, 1, 5, 9);
System.out.println(rangeSearch); // 输出: 4

数组填充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 填充整个数组
int[] arr = new int[5];
Arrays.fill(arr, 10);
System.out.println(Arrays.toString(arr));
// 输出: [10, 10, 10, 10, 10]

// 填充指定范围
int[] arr2 = new int[10];
Arrays.fill(arr2, 2, 6, 99); // 索引[2,6)填充为99
System.out.println(Arrays.toString(arr2));
// 输出: [0, 0, 99, 99, 99, 99, 0, 0, 0, 0]

// 填充二维数组
int[][] matrix = new int[3][3];
for (int[] row : matrix) {
Arrays.fill(row, 1);
}
System.out.println(Arrays.deepToString(matrix));
// 输出: [[1, 1, 1], [1, 1, 1], [1, 1, 1]]

数组复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int[] original = {1, 2, 3, 4, 5};

// 复制全部
int[] copy1 = Arrays.copyOf(original, original.length);
System.out.println(Arrays.toString(copy1)); // [1, 2, 3, 4, 5]

// 复制前3个元素
int[] copy2 = Arrays.copyOf(original, 3);
System.out.println(Arrays.toString(copy2)); // [1, 2, 3]

// 扩展数组超出部分填充默认值
int[] copy3 = Arrays.copyOf(original, 7);
System.out.println(Arrays.toString(copy3)); // [1, 2, 3, 4, 5, 0, 0]

// 复制指定范围
int[] copy4 = Arrays.copyOfRange(original, 1, 4);
System.out.println(Arrays.toString(copy4)); // [2, 3, 4]

// 复制二维数组浅拷贝
int[][] matrix = {{1, 2}, {3, 4}};
int[][] matrixCopy = Arrays.copyOf(matrix, matrix.length);
matrixCopy[0][0] = 99;
System.out.println(matrix[0][0]); // 99 (浅拷贝内部数组共享)

数组比较

1
2
3
4
5
6
7
8
9
10
11
12
13
// 基本类型比较
int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};
int[] arr3 = {1, 2, 4};

System.out.println(Arrays.equals(arr1, arr2)); // true
System.out.println(Arrays.equals(arr1, arr3)); // false

// 多维数组比较
int[][] deep1 = {{1, 2}, {3, 4}};
int[][] deep2 = {{1, 2}, {3, 4}};
System.out.println(Arrays.equals(deep1, deep2)); // false (比较的是引用)
System.out.println(Arrays.deepEquals(deep1, deep2)); // true (深度比较)

并行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 并行排序多线程大数据量时性能更好
int[] largeArray = new int[1000000];
// 填充随机数...
Arrays.parallelSort(largeArray);

// 并行设置值
int[] arr = new int[100];
Arrays.parallelSetAll(arr, i -> i * 2);
System.out.println(Arrays.toString(Arrays.copyOf(arr, 10)));
// [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

// 并行前缀计算累加等
int[] prefix = {1, 2, 3, 4, 5};
Arrays.parallelPrefix(prefix, (x, y) -> x + y);
System.out.println(Arrays.toString(prefix));
// [1, 3, 6, 10, 15]

数组转集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 基本类型数组转List注意不能直接转
int[] intArray = {1, 2, 3, 4, 5};
// List<Integer> list = Arrays.asList(intArray); // 错误会得到List<int[]>

// 正确方式1使用Stream
List<Integer> list1 = Arrays.stream(intArray)
.boxed()
.collect(Collectors.toList());

// 正确方式2使用包装类型数组
Integer[] integerArray = {1, 2, 3, 4, 5};
List<Integer> list2 = Arrays.asList(integerArray); // 正确

// 注意asList返回的是固定大小的List
list2.set(0, 100); // 可以修改元素
// list2.add(6); // 抛出UnsupportedOperationException

// 转换为Set
Set<Integer> set = new HashSet<>(Arrays.asList(integerArray));

时间处理

相关类

1
2
3
4
5
6
7
8
9
10
// Java 1.0: Date类大部分方法已废弃
Date date = new Date();

// Java 1.1: Calendar类仍存在线程安全问题
Calendar calendar = Calendar.getInstance();

// Java 8+: 全新的时间APIJSR-310基于ISO标准
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime dateTime = LocalDateTime.now();
新版时间 APIjava.timeJDK 8+
主要类 描述
LocalDate 日期年-月-日
LocalTime 时间时-分-秒-纳秒
LocalDateTime 日期时间
ZonedDateTime 带时区的日期时间
Instant 时间戳机器时间
Duration 时间段纳秒
Period 时间段
ZoneId 时区
DateTimeFormatter 格式化线程安全

Date

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 1. 创建Date对象
Date now = new Date(); // 当前时间
Date date1 = new Date(0); // 1970-01-01 08:00:00 (时区相关)
Date date2 = new Date(System.currentTimeMillis()); // 使用时间戳

System.out.println(now); // Tue Mar 24 15:30:45 CST 2026

// 2. 常用方法大多已废弃
long time = now.getTime(); // 获取时间戳
System.out.println("时间戳: " + time);

// 3. 比较日期
boolean after = date1.after(date2); // date1 是否在 date2 之后
boolean before = date1.before(date2); // date1 是否在 date2 之前
int compare = date1.compareTo(date2); // 比较
boolean equals = date1.equals(date2); // 是否相等

// 4. 格式化需要SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formatted = sdf.format(now);
System.out.println("格式化: " + formatted);

// 5. 解析字符串
try {
Date parsed = sdf.parse("2024-03-24 15:30:45");
System.out.println("解析结果: " + parsed);
} catch (Exception e) {
e.printStackTrace();
}

// 6. 设置时间戳
Date newDate = new Date();
newDate.setTime(1000000000000L);
System.out.println("设置后: " + newDate);

// 7. 线程安全处理
public class DateUtils {
private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

public static String format(Date date) {
return dateFormat.get().format(date);
}
}

Calendar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 1. 获取Calendar实例
Calendar calendar = Calendar.getInstance();
Calendar gregorian = new GregorianCalendar();

// 2. 获取时间分量
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH); // 0-11注意月份从0开始
int day = calendar.get(Calendar.DAY_OF_MONTH);
int hour = calendar.get(Calendar.HOUR_OF_DAY); // 24小时制
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
int millisecond = calendar.get(Calendar.MILLISECOND);
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); // 1=周日, 2=周一...

System.out.printf("%d-%02d-%02d %02d:%02d:%02d%n", year, month + 1, day, hour, minute, second);

// 3. 设置时间
calendar.set(2024, Calendar.MARCH, 24, 15, 30, 45);
calendar.set(Calendar.YEAR, 2025);
calendar.set(Calendar.MONTH, Calendar.DECEMBER);

// 4. 时间运算
calendar.add(Calendar.DAY_OF_MONTH, 10); // 加10天
calendar.add(Calendar.MONTH, -1); // 减1个月
calendar.roll(Calendar.DAY_OF_MONTH, 5); // 只改变指定字段不影响其他

// 5. 获取最大/最小值
int maxDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
int minDay = calendar.getActualMinimum(Calendar.DAY_OF_MONTH);
System.out.println("当月最大天数: " + maxDay);

// 6. 时区设置
calendar.setTimeZone(TimeZone.getTimeZone("America/New_York"));
TimeZone tz = calendar.getTimeZone();
System.out.println("时区: " + tz.getID());

// 7. 判断闰年
GregorianCalendar gc = new GregorianCalendar();
boolean isLeap = gc.isLeapYear(2024);
System.out.println("2024是闰年: " + isLeap);

// 8. 转换为Date
Date date = calendar.getTime();

LocalDate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 1. 创建LocalDate
LocalDate today = LocalDate.now(); // 当前日期
LocalDate specific = LocalDate.of(2024, 3, 24); // 指定日期
LocalDate parsed = LocalDate.parse("2024-03-24"); // 解析字符串

System.out.println("今天: " + today);
System.out.println("指定: " + specific);

// 2. 获取日期信息
int year = today.getYear();
int month = today.getMonthValue(); // 1-12
Month monthEnum = today.getMonth(); // 枚举类型
int day = today.getDayOfMonth();
int dayOfYear = today.getDayOfYear();
DayOfWeek dayOfWeek = today.getDayOfWeek();

System.out.printf("%d年%d月%d日 %s %d年中的第%d天%n", year, month, day, dayOfWeek, year, dayOfYear);

// 3. 日期运算不可变返回新对象
LocalDate tomorrow = today.plusDays(1);
LocalDate nextWeek = today.plusWeeks(1);
LocalDate nextMonth = today.plusMonths(1);
LocalDate nextYear = today.plusYears(1);

LocalDate yesterday = today.minusDays(1);
LocalDate lastWeek = today.minusWeeks(1);

System.out.println("明天: " + tomorrow);
System.out.println("下周: " + nextWeek);

// 4. 日期比较
boolean isAfter = tomorrow.isAfter(today);
boolean isBefore = yesterday.isBefore(today);
boolean isEqual = tomorrow.isEqual(today);
boolean isLeap = today.isLeapYear(); // 是否闰年

// 5. 获取时间差
LocalDate date1 = LocalDate.of(2024, 1, 1);
LocalDate date2 = LocalDate.of(2024, 12, 31);
long daysBetween = ChronoUnit.DAYS.between(date1, date2);
long monthsBetween = ChronoUnit.MONTHS.between(date1, date2);

System.out.println("相差天数: " + daysBetween);

// 6. 调整日期
LocalDate firstDayOfMonth = today.withDayOfMonth(1);
LocalDate lastDayOfMonth = today.withDayOfMonth(today.lengthOfMonth());
LocalDate firstDayOfYear = today.withDayOfYear(1);
LocalDate nextMonday = today.with(DayOfWeek.MONDAY);

// 7. 判断日期范围
boolean isWeekend = today.getDayOfWeek() == DayOfWeek.SATURDAY || today.getDayOfWeek() == DayOfWeek.SUNDAY;

// 8. 获取月份长度
int daysInMonth = today.lengthOfMonth();
int daysInYear = today.lengthOfYear();

System.out.println("本月天数: " + daysInMonth);

LocalTime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 1. 创建LocalTime
LocalTime now = LocalTime.now(); // 当前时间
LocalTime specific = LocalTime.of(15, 30, 45); // 15:30:45
LocalTime specificWithNano = LocalTime.of(15, 30, 45, 123456789);
LocalTime parsed = LocalTime.parse("15:30:45");

System.out.println("当前时间: " + now);

// 2. 获取时间信息
int hour = now.getHour();
int minute = now.getMinute();
int second = now.getSecond();
int nano = now.getNano();

// 3. 时间运算
LocalTime plusHours = now.plusHours(2);
LocalTime plusMinutes = now.plusMinutes(30);
LocalTime plusSeconds = now.plusSeconds(45);
LocalTime minusHours = now.minusHours(1);

// 4. 时间比较
LocalTime morning = LocalTime.of(9, 0);
LocalTime evening = LocalTime.of(18, 0);

boolean isBefore = morning.isBefore(evening);
boolean isAfter = evening.isAfter(morning);

// 5. 时间差
long hoursBetween = ChronoUnit.HOURS.between(morning, evening);
long minutesBetween = ChronoUnit.MINUTES.between(morning, evening);

System.out.println("工作小时数: " + hoursBetween);

// 6. 特殊时间
LocalTime midnight = LocalTime.MIDNIGHT; // 00:00
LocalTime noon = LocalTime.NOON; // 12:00
LocalTime min = LocalTime.MIN; // 00:00
LocalTime max = LocalTime.MAX; // 23:59:59.999999999

// 7. 判断时间范围
boolean isMorning = now.isAfter(morning) && now.isBefore(noon);

// 8. 截断时间
LocalTime truncatedToMinutes = now.truncatedTo(ChronoUnit.MINUTES);
LocalTime truncatedToHours = now.truncatedTo(ChronoUnit.HOURS);

LocalDateTime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 1. 创建LocalDateTime
LocalDateTime now = LocalDateTime.now();
LocalDateTime specific = LocalDateTime.of(2024, 3, 24, 15, 30, 45);
LocalDateTime fromDateAndTime = LocalDateTime.of(
LocalDate.of(2024, 3, 24),
LocalTime.of(15, 30, 45)
);
LocalDateTime parsed = LocalDateTime.parse("2024-03-24T15:30:45");

// 2. 相互转换
LocalDate date = now.toLocalDate();
LocalTime time = now.toLocalTime();
LocalDateTime fromDate = date.atTime(15, 30); // 日期+时间
LocalDateTime fromTime = time.atDate(date); // 时间+日期

// 3. 日期时间运算
LocalDateTime nextHour = now.plusHours(1);
LocalDateTime nextDay = now.plusDays(1);
LocalDateTime nextMonth = now.plusMonths(1);

// 4. 获取特定字段
int year = now.getYear();
int month = now.getMonthValue();
int day = now.getDayOfMonth();
int hour = now.getHour();
int minute = now.getMinute();

// 5. 修改部分字段
LocalDateTime withYear = now.withYear(2025);
LocalDateTime withMonth = now.withMonth(12);
LocalDateTime withHour = now.withHour(23);

ZonedDateTime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 1. 获取所有时区
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
System.out.println("可用时区数量: " + zoneIds.size());

// 2. 创建ZoneId
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
ZoneId tokyo = ZoneId.of("Asia/Tokyo");
ZoneId ny = ZoneId.of("America/New_York");
ZoneId systemDefault = ZoneId.systemDefault();

// 3. 创建ZonedDateTime
ZonedDateTime nowInShanghai = ZonedDateTime.now(shanghai);
ZonedDateTime nowInTokyo = ZonedDateTime.now(tokyo);
ZonedDateTime nowInNY = ZonedDateTime.now(ny);
ZonedDateTime nowDefault = ZonedDateTime.now();

System.out.println("上海时间: " + nowInShanghai);
System.out.println("东京时间: " + nowInTokyo);
System.out.println("纽约时间: " + nowInNY);

// 4. 转换时区
ZonedDateTime tokyoTime = nowInShanghai.withZoneSameInstant(tokyo);
ZonedDateTime nyTime = nowInShanghai.withZoneSameInstant(ny);

// 5. 时差计算
int offsetHours = shanghai.getRules().getOffset(Instant.now()).getTotalSeconds() / 3600;
System.out.println("上海时区偏移: UTC+" + offsetHours);

// 6. 从LocalDateTime转换
LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zoned = localDateTime.atZone(shanghai);

// 7. 转换为时间戳
Instant instant = nowInShanghai.toInstant();

// 8. 处理夏令时
ZoneId losAngeles = ZoneId.of("America/Los_Angeles");
ZonedDateTime summerTime = ZonedDateTime.of(2024, 3, 10, 2, 0, 0, 0, losAngeles);
System.out.println("夏令时: " + summerTime);

// 9. 获取偏移量
ZoneOffset offset = nowInShanghai.getOffset();
System.out.println("当前偏移: " + offset);

Instant

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 1. 创建Instant
Instant now = Instant.now(); // UTC时间
Instant epoch = Instant.EPOCH; // 1970-01-01T00:00:00Z
Instant fromMillis = Instant.ofEpochMilli(System.currentTimeMillis());
Instant fromSeconds = Instant.ofEpochSecond(1000000000);

System.out.println("当前时间戳: " + now);
System.out.println("时间戳毫秒数: " + now.toEpochMilli());
System.out.println("时间戳秒数: " + now.getEpochSecond());

// 2. 时间运算
Instant later = now.plusSeconds(3600); // 加1小时
Instant earlier = now.minus(1, ChronoUnit.DAYS); // 减1天

// 3. 比较
boolean isAfter = later.isAfter(now);
boolean isBefore = earlier.isBefore(now);

// 4. 转换到其他类型
ZonedDateTime zonedDateTime = now.atZone(ZoneId.systemDefault());
LocalDateTime localDateTime = LocalDateTime.ofInstant(now, ZoneId.systemDefault());

// 5. 计算时间差
Instant start = Instant.now();
Thread.sleep(1000);// ... 执行操作
Instant end = Instant.now();

Duration duration = Duration.between(start, end);
System.out.println("耗时: " + duration.toMillis() + "ms");
System.out.println("耗时: " + duration.getNano() + "ns");

Duration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ========== Duration: 秒纳秒级别的时间间隔 ==========
// 创建Duration
Duration d1 = Duration.ofDays(1); // 24小时
Duration d2 = Duration.ofHours(2); // 2小时
Duration d3 = Duration.ofMinutes(30); // 30分钟
Duration d4 = Duration.ofSeconds(45); // 45秒
Duration d5 = Duration.ofMillis(500); // 500毫秒
Duration d6 = Duration.ofNanos(1000000); // 1毫秒

// 计算时间差
LocalTime start = LocalTime.of(9, 0);
LocalTime end = LocalTime.of(17, 30);
Duration workDuration = Duration.between(start, end);

System.out.println("工作时长: " + workDuration.toHours() + "小时");
System.out.println("工作时长: " + workDuration.toMinutes() + "分钟");

// Duration运算
Duration total = d1.plus(d2).minus(d3);
Duration multiplied = d1.multipliedBy(2);
Duration divided = d1.dividedBy(2);

Period

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ========== Period: 年日级别的时间间隔 ==========
// 创建Period
Period p1 = Period.ofYears(1); // 1年
Period p2 = Period.ofMonths(3); // 3个月
Period p3 = Period.ofDays(10); // 10天
Period p4 = Period.of(1, 2, 15); // 1年2个月15天

// 计算日期差
LocalDate date1 = LocalDate.of(2024, 1, 1);
LocalDate date2 = LocalDate.of(2025, 3, 24);
Period period = Period.between(date1, date2);

System.out.printf("相差: %d年 %d个月 %d天%n", period.getYears(), period.getMonths(), period.getDays());

// Period运算
Period totalPeriod = p1.plus(p2).minus(p3);
Period normalized = p1.normalized(); // 标准化如15个月 → 1年3个月

DateTimeFormatter

常用模式字母
字母模版 描述
yyyy 年份
MM 月份01-12
dd 日期01-31
HH 小时00-23
hh 小时01-12
mm 分钟00-59
ss 00-59
SSS 毫秒
EEEE 星期几完整
E 星期几缩写
a 上午/下午
z 时区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
LocalDateTime now = LocalDateTime.now();

// 1. 预定义格式
DateTimeFormatter isoDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
DateTimeFormatter isoDate = DateTimeFormatter.ISO_LOCAL_DATE;
DateTimeFormatter isoTime = DateTimeFormatter.ISO_LOCAL_TIME;

System.out.println("ISO日期时间: " + now.format(isoDateTime));
System.out.println("ISO日期: " + now.format(isoDate));

// 2. 本地化格式
DateTimeFormatter longFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
DateTimeFormatter mediumFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
DateTimeFormatter shortFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);

System.out.println("长格式: " + now.format(longFormat));
System.out.println("中格式: " + now.format(mediumFormat));
System.out.println("短格式: " + now.format(shortFormat));

// 3. 自定义格式最常用
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
DateTimeFormatter formatter4 = DateTimeFormatter.ofPattern("yyyy/MM/dd EEEE HH:mm", Locale.CHINA);

System.out.println("自定义1: " + now.format(formatter1));
System.out.println("自定义2: " + now.format(formatter2));
System.out.println("自定义3: " + now.format(formatter3));
System.out.println("自定义4: " + now.format(formatter4));

// 4. 解析字符串
String dateStr = "2024-03-24 15:30:45";
LocalDateTime parsed = LocalDateTime.parse(dateStr, formatter1);
System.out.println("解析结果: " + parsed);

// 5. 链式调用
String result = now.format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
.withLocale(Locale.CHINA)
);

文件操作

基本概念

File 类是 Java早期JDK 1.0提供的文件操作类用于表示文件或目录的路径名

Files类是 JDK 1.7 引入的NIO.2New I/O的一部分提供了更强大更便捷的文件操作API

特性 File类 Files类
引入版本 JDK 1.0 JDK 1.7 (NIO.2)
核心接口 File 对象 Path 接口
异常处理 返回 boolean 抛出 IOException
符号链接 有限支持 完整支持
批量操作 不支持 支持流式操作
文件监控 不支持 支持WatchService
性能 较慢 更快利用OS特性
异步 IO 不支持 支持 AsynchronousFileChannel
内存映射 不支持 支持 FileChannel

路径分隔符

1
2
3
4
5
6
// 平台相关的路径分隔符
String separator = File.separator; // Windows: \ Linux: /
String pathSeparator = File.pathSeparator; // Windows: ; Linux: :

// 推荐使用
File file = new File("C:" + File.separator + "test" + File.separator + "file.txt");

创建对象

⭐⭐ File 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1. 直接传入路径
File file1 = new File("C:/test.txt");
File file2 = new File("test.txt"); // 相对路径

// 2. 父路径 + 子路径
File file3 = new File("C:/test", "file.txt");

// 3. 父 File 对象 + 子路径
File parent = new File("C:/test");
File file4 = new File(parent, "file.txt");

// 4. URI 方式
File file5 = new File(URI.create("file:/C:/test.txt"));

⭐⭐ Files 对象

1
Path path = Paths.get("example.txt");

文件属性

⭐⭐ File 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
File file = new File("example.txt");

// 判断是否存在
boolean exists = file.exists();

// 判断是否为文件
boolean isFile = file.isFile();

// 判断是否为目录
boolean isDirectory = file.isDirectory();

// 判断是否可读/可写/可执行
boolean canRead = file.canRead();
boolean canWrite = file.canWrite();
boolean canExecute = file.canExecute();

// 判断是否为隐藏文件
boolean isHidden = file.isHidden();

// 判断是否为绝对路径
boolean isAbsolute = file.isAbsolute();

// 获取文件名包含扩展名
String name = file.getName(); // "example.txt"

// 获取路径
String path = file.getPath(); // 构造时的路径
String absolutePath = file.getAbsolutePath(); // 绝对路径
String canonicalPath = file.getCanonicalPath(); // 规范路径

// 获取父路径
String parent = file.getParent(); // "C:\test"
File parentFile = file.getParentFile();

// 获取文件大小字节
long length = file.length();

// 最后修改时间毫秒
long lastModified = file.lastModified();

// 获取文件系统根目录
File[] roots = File.listRoots();

⭐⭐ Files 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
Path path = Paths.get("test.txt");

// 判断是否存在
boolean exists = Files.exists(path);
boolean notExists = Files.notExists(path);

// 判断类型
boolean isDir = Files.isDirectory(path);
boolean isRegFile = Files.isRegularFile(path);
boolean isSymLink = Files.isSymbolicLink(path);

// 判断权限
boolean isReadable = Files.isReadable(path);
boolean isWritable = Files.isWritable(path);
boolean isExecutable = Files.isExecutable(path);

// 获取文件大小
long size = Files.size(path); // 字节

// 获取最后修改时间
FileTime lastModified = Files.getLastModifiedTime(path);

// 获取文件所有者
UserPrincipal owner = Files.getOwner(path);

// 文件存储信息
Path path = Paths.get(".");
FileStore store = Files.getFileStore(path);
System.out.println("文件系统" + store.name());
System.out.println("类型" + store.type());
System.out.println("总空间" + store.getTotalSpace() / (1024*1024*1024) + " GB");
System.out.println("可用空间" + store.getUsableSpace() / (1024*1024*1024) + " GB");
System.out.println("未分配空间" + store.getUnallocatedSpace() / (1024*1024*1024) + " GB");

// 基础属性跨平台
BasicFileAttributes basic = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("创建时间" + basic.creationTime());
System.out.println("最后访问" + basic.lastAccessTime());
System.out.println("最后修改" + basic.lastModifiedTime());
System.out.println("文件大小" + basic.size());
System.out.println("是否为目录" + basic.isDirectory());
System.out.println("是否为普通文件" + basic.isRegularFile());
System.out.println("是否为符号链接" + basic.isSymbolicLink());

// POSIX 属性Linux/Unix
if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
PosixFileAttributes posix = Files.readAttributes(path, PosixFileAttributes.class);
System.out.println("所有者" + posix.owner());
System.out.println("权限" + PosixFilePermissions.toString(posix.permissions()));
System.out.println("组" + posix.group());
}

// DOS 属性Windows
DosFileAttributes dos = Files.readAttributes(path, DosFileAttributes.class);
System.out.println("只读" + dos.isReadOnly());
System.out.println("隐藏" + dos.isHidden());
System.out.println("系统" + dos.isSystem());

// 设置最后修改时间
Files.setLastModifiedTime(path, FileTime.fromMillis(System.currentTimeMillis()));

// 设置所有者
UserPrincipal user = FileSystems.getDefault()
.getUserPrincipalLookupService()
.lookupPrincipalByName("username");
Files.setOwner(path, user);

// 设置 POSIX 权限
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-r--r--");
Files.setPosixFilePermissions(path, perms);

// 设置 DOS 属性
Files.setAttribute(path, "dos:hidden", true);
Files.setAttribute(path, "dos:readonly", false);

创建操作

⭐⭐ File 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
File file = new File("newfile.txt");

// 创建文件
boolean created = file.createNewFile();

// 创建目录
File dir = new File("newdir");
boolean mkdir = dir.mkdir(); // 创建单级目录

// 创建多级目录
File dirs = new File("parent/child/grandchild");
boolean mkdirs = dirs.mkdirs(); // 创建多级目录

// 创建临时文件
File tempFile;
try {
tempFile = File.createTempFile("prefix", ".tmp");
tempFile.deleteOnExit(); // 程序退出时删除
} catch (IOException e) {
e.printStackTrace();
}

⭐⭐ Files 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Path path = Paths.get("example.txt");

// 创建文件如果文件已存在则抛出异常
try {
Path file = Files.createFile(path);
System.out.println("创建成功" + file);
} catch (FileAlreadyExistsException e) {
System.out.println("文件已存在");
} catch (IOException e) {
e.printStackTrace();
}

// 创建目录单级
Path dir = Paths.get("mydir");
Path newDir = Files.createDirectory(dir);

// 创建多级目录
Path multiDir = Paths.get("parent/child/grandchild");
Path newMultiDir = Files.createDirectories(multiDir);

// 创建临时文件
Path tempFile = Files.createTempFile("prefix", ".tmp");
Path tempDir = Files.createTempDirectory("tempdir");

// 指定目录创建临时文件
Path tempFile2 = Files.createTempFile(Paths.get("/tmp"), "prefix", ".tmp");

// 原子创建文件
Path tempFile = Files.createTempFile("temp", ".tmp");
Files.write(tempFile, data);
Files.move(tempFile, target, StandardCopyOption.ATOMIC_MOVE);

删除操作

⭐⭐ File 对象

1
2
3
4
5
// 删除文件/目录目录必须为空
boolean deleted = file.delete();

// 程序退出时删除
file.deleteOnExit();

⭐⭐ Files 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Path path = Paths.get("test.txt");

// 删除文件/空目录不存在时抛出异常
try {
Files.delete(path);
} catch (NoSuchFileException e) {
System.out.println("文件不存在");
} catch (DirectoryNotEmptyException e) {
System.out.println("目录非空");
}

// 删除文件/目录不存在时不抛出异常
boolean deleted = Files.deleteIfExists(path);
if (deleted) {
System.out.println("删除成功");
}

移动复制

⭐⭐ File 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 重命名/移动文件
File dest = new File("renamed.txt");
boolean renamed = file.renameTo(dest);

// 文件复制
public static void copyFile(File source, File dest) throws IOException {
if (!source.exists() || !source.isFile()) {
throw new IllegalArgumentException("源文件不存在");
}

try (FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(dest)) {

byte[] buffer = new byte[8192];
int length;
while ((length = fis.read(buffer)) > 0) {
fos.write(buffer, 0, length);
}

System.out.println("文件复制成功");
}
}

⭐⭐ Files 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Path source = Paths.get("source.txt");
Path target = Paths.get("target.txt");

// 复制文件
// 标准复制
Path copy = Files.copy(source, target);

// 指定选项复制
Path copy2 = Files.copy(source, target,
StandardCopyOption.REPLACE_EXISTING, // 覆盖已存在文件
StandardCopyOption.COPY_ATTRIBUTES); // 复制文件属性

// 移动/重命名文件
Path moved = Files.move(source, target,
StandardCopyOption.REPLACE_EXISTING, // 覆盖
StandardCopyOption.ATOMIC_MOVE); // 原子移动

// 复制输入流到文件
try (InputStream in = new URL("https://example.com").openStream()) {
Files.copy(in, Paths.get("downloaded.html"),
StandardCopyOption.REPLACE_EXISTING);
}

// 复制文件到输出流
try (OutputStream out = new FileOutputStream("copy.txt")) {
Files.copy(Paths.get("original.txt"), out);
}

// 文件复制带进度
public static void copyWithProgress(Path source, Path target) throws IOException {
long total = Files.size(source);
long[] copied = {0};

try (InputStream in = Files.newInputStream(source);
OutputStream out = Files.newOutputStream(target)) {

byte[] buffer = new byte[8192];
int length;

while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
copied[0] += length;

// 显示进度
int percent = (int) (copied[0] * 100 / total);
System.out.printf("\r进度%d%%", percent);
}
}

System.out.println("\n复制完成");
}

遍历目录

⭐⭐ File 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void listAllFiles(File dir, int level) {
if (dir == null || !dir.exists()) {
return;
}

// 添加缩进
String indent = " ".repeat(level);

if (dir.isFile()) {
System.out.println(indent + "📄 " + dir.getName() + " (" + dir.length() + " bytes)");
} else if (dir.isDirectory()) {
System.out.println(indent + "📁 " + dir.getName());
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
listAllFiles(file, level + 1);
}
}
}
}

⭐⭐ Files 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Path dir = Paths.get(".");

// 简单列出
try (Stream<Path> stream = Files.list(dir)) {
stream.forEach(System.out::println);
}

// 过滤并列出
try (Stream<Path> stream = Files.list(dir)) {
stream.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".txt"))
.forEach(System.out::println);
}

// 遍历目录树
Path start = Paths.get("src");

// 深度优先遍历
try (Stream<Path> stream = Files.walk(start)) {
stream.filter(Files::isRegularFile)
.forEach(System.out::println);
}

// 限制深度
try (Stream<Path> stream = Files.walk(start, 2)) {
stream.forEach(System.out::println);
}

// 目录树结构
public static void printTree(Path path, int level) throws IOException {
String indent = " ".repeat(level);

if (Files.isDirectory(path)) {
System.out.println(indent + "📁 " + path.getFileName());

try (Stream<Path> children = Files.list(path)) {
List<Path> sorted = children.sorted().collect(Collectors.toList());
for (Path child : sorted) {
printTree(child, level + 1);
}
}
} else {
long size = Files.size(path);
System.out.println(indent + "📄 " + path.getFileName() +
" (" + size + " bytes)");
}
}

文件搜索

⭐⭐ File 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// File 类 - 使用 FileFilter
File dir = new File(".");
File[] txtFiles = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(".txt");
}
});

// 或使用 FilenameFilter
File[] txtFiles2 = dir.listFiles((d, name) -> name.endsWith(".txt"));

// 封装方法
public static List<File> searchFiles(File dir, String keyword) {
List<File> result = new ArrayList<>();

if (dir == null || !dir.exists()) {
return result;
}

if (dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
result.addAll(searchFiles(file, keyword));
} else if (file.getName().contains(keyword)) {
result.add(file);
}
}
}
}

return result;
}

⭐⭐ Files 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 方式1直接过滤
Path dir = Paths.get(".");
try (Stream<Path> stream = Files.list(dir)) {

stream.filter(p -> p.toString().endsWith(".txt"))
.forEach(System.out::println);
}

// 方式2使用 DirectoryStream
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.txt")) {
for (Path entry : stream) {
System.out.println(entry);
}
}

// 查找特定文件
try (Stream<Path> stream = Files.find(start, Integer.MAX_VALUE,
(path, attrs) -> attrs.isRegularFile() && path.toString().endsWith(".java"))) {
stream.forEach(System.out::println);
}

文件比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static boolean compareFiles(Path file1, Path file2) throws IOException {
// 先比较大小
if (Files.size(file1) != Files.size(file2)) {
return false;
}

// 比较内容
try (InputStream in1 = Files.newInputStream(file1);
InputStream in2 = Files.newInputStream(file2)) {

byte[] buf1 = new byte[8192];
byte[] buf2 = new byte[8192];
int len;

while ((len = in1.read(buf1)) != -1) {
in2.read(buf2);
if (!Arrays.equals(buf1, buf2)) {
return false;
}
}

return true;
}
}

读写内容

⭐⭐ 读取内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Path path = Paths.get("data.txt");

// 读取所有字节
byte[] bytes = Files.readAllBytes(path);
String content = new String(bytes, StandardCharsets.UTF_8);

// 读取所有行小文件
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
lines.forEach(System.out::println);

// 按行读取流式处理适合大文件
try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
lines.filter(line -> line.contains("keyword"))
.forEach(System.out::println);
}

⭐⭐ 写入内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Path path = Paths.get("output.txt");

// 写入字节数组
byte[] data = "Hello World".getBytes(StandardCharsets.UTF_8);
Files.write(path, data);

// 写入字符串列表
List<String> lines = Arrays.asList("Line 1", "Line 2", "Line 3");
Files.write(path, lines, StandardCharsets.UTF_8);

// 追加内容
Files.write(path, "追加的内容".getBytes(),
StandardOpenOption.APPEND);

// 创建新文件并写入
Files.write(path, lines,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);

⭐⭐ 批量文件处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void batchProcess(Path dir, String extension) throws IOException {
try (Stream<Path> stream = Files.walk(dir)) {
stream.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(extension))
.forEach(file -> {
try {
// 处理每个文件
System.out.println("处理" + file);

// 读取内容
List<String> lines = Files.readAllLines(file);

// 处理内容示例添加行号
List<String> numbered = new ArrayList<>();
for (int i = 0; i < lines.size(); i++) {
numbered.add((i + 1) + ": " + lines.get(i));
}

// 写回文件
Files.write(file, numbered);

} catch (IOException e) {
System.err.println("处理文件失败" + file);
}
});
}
}

符号链接

⭐⭐ File 对象

1
2
3
4
File link = new File("link");
File target = new File("target");
// 只能检查是否为符号链接
boolean isLink = Files.isSymbolicLink(link.toPath()); // 需要转换

⭐⭐ Files 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Path link = Paths.get("link");
Path target = Paths.get("target.txt");

// 创建符号链接
Files.createSymbolicLink(link, target);

// 创建硬链接
Files.createLink(link, target);

// 读取符号链接的目标
Path realPath = Files.readSymbolicLink(link);

// 获取真实路径解析符号链接
Path real = target.toRealPath(LinkOption.NOFOLLOW_LINKS);

目录监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Path dir = Paths.get("watchdir");
WatchService watchService = FileSystems.getDefault().newWatchService();

// 注册监控事件
dir.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);

// 处理事件
while (true) {
WatchKey key = watchService.take();

for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();

if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}

Path filename = (Path) event.context();
System.out.println(kind.name() + ": " + filename);
}

boolean valid = key.reset();
if (!valid) {
break;
}
}

综合其他

⭐⭐ OpenOptions 选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 常用打开选项
StandardOpenOption.READ // 读
StandardOpenOption.WRITE // 写
StandardOpenOption.APPEND // 追加
StandardOpenOption.CREATE // 不存在则创建
StandardOpenOption.CREATE_NEW // 创建新文件已存在则失败
StandardOpenOption.TRUNCATE_EXISTING // 截断现有文件
StandardOpenOption.DELETE_ON_CLOSE // 关闭时删除
StandardOpenOption.SPARSE // 稀疏文件
StandardOpenOption.SYNC // 同步写入
StandardOpenOption.DSYNC // 同步数据写入

// 使用示例
try (SeekableByteChannel channel = Files.newByteChannel(path,
EnumSet.of(StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.APPEND))) {
// 操作 channel
}

系统相关

System 类是 Java 提供的系统相关工具类位于 java.lang 包中提供了访问系统属性环境变量加载类库数组复制等功能

属性与变量

⭐⭐ 系统属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 获取所有系统属性
Properties props = System.getProperties();
props.list(System.out);

// 获取单个系统属性
String javaVersion = System.getProperty("java.version");
String osName = System.getProperty("os.name");
String userName = System.getProperty("user.name");
String userDir = System.getProperty("user.dir");
String fileSeparator = System.getProperty("file.separator");
String lineSeparator = System.getProperty("line.separator");

System.out.println("Java版本: " + javaVersion);
System.out.println("操作系统: " + osName);
System.out.println("用户名: " + userName);
System.out.println("当前目录: " + userDir);
System.out.println("文件分隔符: " + fileSeparator);

// 常用系统属性
String[] commonProps = {
"java.version", // Java运行时环境版本
"java.home", // Java安装目录
"java.io.tmpdir", // 临时文件目录
"os.arch", // 操作系统架构
"os.version", // 操作系统版本
"user.home", // 用户主目录
"user.dir", // 用户当前工作目录
"file.encoding", // 文件编码
"path.separator", // 路径分隔符Windows: ; Linux: :
};

for (String prop : commonProps) {
System.out.println(prop + " = " + System.getProperty(prop));
}

// 设置系统属性需要权限
System.setProperty("my.custom.property", "自定义值");
System.out.println(System.getProperty("my.custom.property"));

⭐⭐ 环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 获取所有环境变量
Map<String, String> env = System.getenv();
for (Map.Entry<String, String> entry : env.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}

// 获取单个环境变量
String path = System.getenv("PATH");
String javaHome = System.getenv("JAVA_HOME");
String os = System.getenv("OS");

System.out.println("PATH: " + path);
System.out.println("JAVA_HOME: " + javaHome);
System.out.println("操作系统: " + os);

// Windows系统常用环境变量
String programFiles = System.getenv("ProgramFiles");
String appData = System.getenv("APPDATA");
System.out.println("Program Files: " + programFiles);
System.out.println("AppData: " + appData);

系统时间

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1. 当前时间毫秒数从1970-01-01 UTC开始
long currentTime = System.currentTimeMillis();
System.out.println("当前时间戳: " + currentTime);
System.out.println("当前时间: " + new Date(currentTime));

// 2. 纳秒级时间用于测量耗时
long startTime = System.nanoTime();
// 执行某些操作
Thread.sleep(1000);
long endTime = System.nanoTime();
long duration = endTime - startTime;
System.out.println("耗时: " + duration + " 纳秒");
System.out.println("耗时: " + duration / 1_000_000 + " 毫秒");

垃圾回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 建议JVM进行垃圾回收不保证立即执行
System.gc();

// 运行finalization不推荐使用
System.runFinalization();

// 示例监控内存使用
Runtime runtime = Runtime.getRuntime();

long maxMemory = runtime.maxMemory();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();

System.out.printf("最大内存: %.2f MB%n", maxMemory / 1024.0 / 1024);
System.out.printf("已分配内存: %.2f MB%n", totalMemory / 1024.0 / 1024);
System.out.printf("空闲内存: %.2f MB%n", freeMemory / 1024.0 / 1024);
System.out.printf("已使用内存: %.2f MB%n", (totalMemory - freeMemory) / 1024.0 / 1024);

// 创建大量对象触发GC
List<byte[]> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(new byte[1024 * 1024]); // 1MB
if (i % 100 == 0) {
System.out.println("创建 " + (i + 1) + " MB 对象");
System.out.println("空闲内存: " + runtime.freeMemory() / 1024 / 1024 + " MB");
}
}

list.clear();
System.gc(); // 建议GC
Thread.sleep(1000);
System.out.println("GC后空闲内存: " + runtime.freeMemory() / 1024 / 1024 + " MB");

安全管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 1. 获取安全管理器
SecurityManager securityManager = System.getSecurityManager();

// 2. 设置安全管理器需要在启动时设置
// System.setSecurityManager(new SecurityManager());

// 3. 退出虚拟机
// System.exit(0); // 正常退出
// System.exit(1); // 异常退出

// 4. 注册关闭钩子JVM关闭时执行
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("JVM正在关闭执行清理工作...");
// 关闭数据库连接保存状态等
}));

// 5. 示例模拟应用关闭
public class ShutdownDemo {
public static void main(String[] args) {
// 注册多个关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("钩子1: 保存配置文件");
}));

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("钩子2: 关闭数据库连接");
}));

System.out.println("应用运行中...");

// 模拟延迟后退出
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("准备退出");
System.exit(0);
}
}

输入输出

I/OInput/Output Stream是 Java 中用于处理输入输出操作的核心机制它提供了一种统一的方式来读取数据源如文件网络连接内存等和写入数据到目标位置

Stream是一组有序的数据序列可以理解为数据在程序和数据源/目标之间的管道程序可以从流中读取数据输入流也可以向流中写入数据输出流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
java.io

├── 字节流 (Byte Streams) - 处理二进制数据
│ │
│ ├── 字节输入流
│ │ └── java.io.InputStream (抽象基类)
│ │ ├── FileInputStream (文件输入流)
│ │ ├── ByteArrayInputStream (字节数组输入流)
│ │ ├── StringBufferInputStream (已废弃字符串输入流)
│ │ ├── PipedInputStream (管道输入流)
│ │ ├── SequenceInputStream (合并输入流)
│ │ ├── ObjectInputStream (对象输入流)
│ │ ├── FilterInputStream (过滤输入流基类)
│ │ │ ├── BufferedInputStream (缓冲输入流)
│ │ │ ├── DataInputStream (数据输入流)
│ │ │ ├── PushbackInputStream (回推输入流)
│ │ │ ├── LineNumberInputStream (已废弃行号输入流)
│ │ │ └── CipherInputStream (加密输入流javax.crypto)
│ │ ├── AudioInputStream (音频输入流javax.sound)
│ │ └── ZipInputStream (ZIP压缩输入流java.util.zip)
│ │
│ └── 字节输出流
│ └── java.io.OutputStream (抽象基类)
│ ├── FileOutputStream (文件输出流)
│ ├── ByteArrayOutputStream (字节数组输出流)
│ ├── PipedOutputStream (管道输出流)
│ ├── ObjectOutputStream (对象输出流)
│ ├── FilterOutputStream (过滤输出流基类)
│ │ ├── BufferedOutputStream (缓冲输出流)
│ │ ├── DataOutputStream (数据输出流)
│ │ ├── PrintStream (打印流System.out/err)
│ │ └── CipherOutputStream (加密输出流javax.crypto)
│ └── ZipOutputStream (ZIP压缩输出流java.util.zip)

├── 字符流 (Character Streams) - 处理文本数据
│ │
│ ├── 字符输入流
│ │ └── java.io.Reader (抽象基类)
│ │ ├── InputStreamReader (字节→字符转换流)
│ │ │ └── FileReader (文件字符输入流)
│ │ ├── BufferedReader (缓冲字符输入流)
│ │ │ └── LineNumberReader (行号字符输入流)
│ │ ├── CharArrayReader (字符数组输入流)
│ │ ├── StringReader (字符串输入流)
│ │ ├── PipedReader (管道字符输入流)
│ │ └── FilterReader (过滤字符输入流)
│ │ └── PushbackReader (回推字符输入流)
│ │
│ └── 字符输出流
│ └── java.io.Writer (抽象基类)
│ ├── OutputStreamWriter (字符→字节转换流)
│ │ └── FileWriter (文件字符输出流)
│ ├── BufferedWriter (缓冲字符输出流)
│ ├── CharArrayWriter (字符数组输出流)
│ ├── StringWriter (字符串输出流)
│ ├── PipedWriter (管道字符输出流)
│ ├── FilterWriter (过滤字符输出流抽象类)
│ └── PrintWriter (打印字符输出流)

├── 压缩流 (Compression Streams) - 处理ZIP/JAR格式
│ │
│ ├── DeflaterInputStream (压缩输入流)
│ ├── InflaterInputStream (解压输入流)
│ │ ├── ZipEntry (ZIP条目表示压缩文件中的单个文件)
│ │ ├── ZipFile (ZIP文件直接访问类)
│ │ ├── ZipInputStream (ZIP解压输入流)
│ │ ├── ZipOutputStream (ZIP压缩输出流)
│ │ ├── GZIPInputStream (GZIP解压输入流)
│ │ ├── GZIPOutputStream (GZIP压缩输出流)
│ │ ├── CheckedInputStream (校验和输入流)
│ │ └── CheckedOutputStream (校验和输出流)
│ │
│ └── JAR扩展 (java.util.jar)
│ ├── JarEntry (JAR条目继承自ZipEntry)
│ ├── JarFile (JAR文件访问类继承自ZipFile)
│ ├── JarInputStream (JAR输入流继承自ZipInputStream)
│ └── JarOutputStream (JAR输出流继承自ZipOutputStream)

└── 标准流
├── System.in (InputStream) - 标准输入
├── System.out (PrintStream) - 标准输出
└── System.err (PrintStream) - 标准错误

标准流

Java 程序与控制台终端 之间的预定义流由 JVM 自动打开可直接使用无需手动创建流对象

标准输入流

System.in 标准输入流InputStream 类型通常从键盘读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 基础使用
Scanner scanner = new Scanner(System.in);

System.out.print("请输入姓名: ");
String name = scanner.nextLine();

System.out.print("请输入年龄: ");
int age = scanner.nextInt();

System.out.printf("姓名: %s, 年龄: %d%n", name, age);

// 重定向输入从文件读取
try {
System.setIn(new FileInputStream("input.txt"));
Scanner fileScanner = new Scanner(System.in);
while (fileScanner.hasNextLine()) {
System.out.println(fileScanner.nextLine());
}
fileScanner.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}

标准输出流

System.out 标准输出流PrintStream 类型向控制台输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 基本输出
System.out.print("不换行输出");
System.out.println("换行输出");
System.out.printf("格式化输出: %d, %.2f, %s%n", 10, 3.14159, "Java");

// 格式化输出详细示例
int age = 25;
String name = "张三";
double salary = 12345.6789;

// 常用格式化符
System.out.printf("姓名: %s, 年龄: %d, 工资: %.2f%n", name, age, salary);
System.out.printf("十六进制: %x%n", 255); // ff
System.out.printf("八进制: %o%n", 255); // 377
System.out.printf("科学计数法: %e%n", 12345.67); // 1.234567e+04
System.out.printf("布尔值: %b%n", true); // true

// 宽度和对齐
System.out.printf("%-10s %5d %8.2f%n", "张三", 25, 12345.67);
System.out.printf("%-10s %5d %8.2f%n", "李四", 30, 9876.54);
// 输出:
// 张三 25 12345.67
// 李四 30 9876.54

// 日期时间格式化
System.out.printf("当前时间: %tF %<tT%n", System.currentTimeMillis());
// 输出: 当前时间: 2024-03-24 15:30:45

标准错误流

System.err 标准错误流PrintStream 类型输出错误信息

1
2
3
4
5
6
7
8
9
10
11
// 错误输出通常显示为红色
System.err.println("错误信息");
System.err.printf("错误码: %d%n", 404);

// 实际应用异常处理
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.err.println("计算错误: " + e.getMessage());
e.printStackTrace(System.err); // 将堆栈输出到错误流
}

字节流

以字节8 位为单位进行数据读写的流InputStream输入OutputStream输出

  • 处理二进制数据如图片音频视频任意文件
  • 无缓冲通常需要手动包装为缓冲流提升性能

InputStream

InputStream 是所有字节输入流的抽象基类定义了读取字节数据的基本方法它本身不能直接实例化但提供了统一的接口规范

⭐⭐ 核心方法

1
2
3
4
5
6
7
8
9
10
11
int read(): 读取一个字节返回 0-255返回 -1 表示结束

int read(byte[] b): 读取字节到数组返回实际读取的字节数

int read(byte[] b, int off, int len): 读取 len 个字节到数组指定位置

long skip(long n): 跳过 n 个字节

int available(): 返回可读取的字节数

void close(): 关闭流释放系统资源

OutputStream

OutputStream 是所有字节输出流的抽象基类定义了向目的地写入字节数据的基本方法它本身不能直接实例化但提供了统一的接口规范

⭐⭐ 核心方法

1
2
3
4
5
6
7
8
9
void write(int b): 写入一个字节

void write(byte[] b): 写入字节数组

void write(byte[] b, int off, int len): 写入字节数组的指定部分

void flush(): 刷新缓冲区强制写出所有缓冲的数据

void close(): 关闭流释放系统资源

文件输入输出流

FileInputStream 用于从文件系统中的文件读取原始字节数据它适合读取图像音频视频等二进制文件也适合读取文本文件但推荐用字符流处理文本

  • 从文件系统读取字节数据
  • 支持顺序访问
  • 可以读取任何类型的文件
  • 必须处理FileNotFoundException和IOException

FileOutputStream 用于向文件系统写入原始字节数据它可以将数据写入文件支持覆盖和追加两种模式

  • 写入数据到文件
  • 支持覆盖模式默认和追加模式
  • 可以写入任何类型的字节数据
  • 自动创建文件如果不存在
  • 必须处理IOException

⭐⭐ 读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.io.*;

public class FileInputStreamDemo {
public static void main(String[] args) {
FileInputStream fis = null; // 声明文件输入流对象

try {
// 创建FileInputStream对象指定要读取的文件路径
fis = new FileInputStream("example.txt");

int byteData; // 用于存储读取的字节数据

// read()方法返回-1表示文件结束
// 循环读取文件中的每个字节
while ((byteData = fis.read()) != -1) {
// 将读取的字节转换为字符并打印
// 注意这里假设文件内容是ASCII或UTF-8编码的文本
System.out.print((char) byteData);
}

} catch (FileNotFoundException e) {
// 文件未找到时的异常处理
System.out.println("文件未找到: " + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
// I/O操作异常处理
System.out.println("读取文件时发生错误: " + e.getMessage());
e.printStackTrace();
} finally {
// 无论是否发生异常都要确保流被关闭
if (fis != null) {
try {
fis.close(); // 关闭流释放系统资源
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

⭐⭐ 批量读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.*;

public class FileInputStreamBatchDemo {
public static void main(String[] args) {
// 使用try-with-resources自动关闭流Java 7+
try (FileInputStream fis = new FileInputStream("example.txt")) {

byte[] buffer = new byte[1024]; // 创建缓冲区一次读取1024字节
int bytesRead; // 实际读取的字节数

// 循环读取每次读取最多1024字节
while ((bytesRead = fis.read(buffer)) != -1) {
// 将读取的字节数组转换为字符串并打印
// 参数字节数组起始位置实际长度
String content = new String(buffer, 0, bytesRead);
System.out.print(content);
}

} catch (FileNotFoundException e) {
System.out.println("文件未找到: " + e.getMessage());
} catch (IOException e) {
System.out.println("读取文件时发生错误: " + e.getMessage());
}
// try-with-resources会自动调用close()方法
}
}

⭐⭐ 覆盖模式每次写入都会清空原有内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void writeWithOverwrite() {
// 默认false或不写参数表示覆盖模式
try (FileOutputStream fos = new FileOutputStream("output.txt")) {

// 写入单个字节
fos.write(65); // 写入字符'A'ASCII码65
fos.write(66); // 写入字符'B'
fos.write(67); // 写入字符'C'

// 写入字节数组
byte[] bytes = "Hello, World!".getBytes();
fos.write(bytes);

// 写入字节数组的指定部分
byte[] partBytes = "Java Programming".getBytes();
fos.write(partBytes, 0, 4); // 只写入前4个字节"Java"

System.out.println("覆盖模式写入完成");

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 追加模式在文件末尾添加内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void writeWithAppend() {
// 第二个参数true表示追加模式
try (FileOutputStream fos = new FileOutputStream("output.txt", true)) {

// 写入换行符
fos.write('\n');

// 写入追加内容
String appendContent = "This is appended content.\n";
fos.write(appendContent.getBytes());

// 写入更多内容
String moreContent = "Another line of text.";
fos.write(moreContent.getBytes());

System.out.println("追加模式写入完成");

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 使用缓冲区批量写入提高性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void writeWithBuffer() {
try (FileOutputStream fos = new FileOutputStream("largefile.txt")) {

// 创建缓冲区批量数据
byte[] buffer = new byte[8192]; // 8KB缓冲区

// 模拟生成大量数据
for (int i = 0; i < 100; i++) {
String line = "这是第 " + (i + 1) + " 行数据\n";
byte[] lineBytes = line.getBytes();

// 如果数据超过缓冲区大小分批写入
if (lineBytes.length > buffer.length) {
fos.write(lineBytes); // 直接写入
} else {
// 将数据复制到缓冲区
System.arraycopy(lineBytes, 0, buffer, 0, lineBytes.length);
fos.write(buffer, 0, lineBytes.length); // 写入缓冲区数据
}
}

System.out.println("批量写入完成");

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 写入不同类型的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void writeDifferentTypes() {
try (FileOutputStream fos = new FileOutputStream("mixed.dat")) {

// 写入整数需要转换为字节
int number = 12345;
fos.write(number >> 24); // 写入高位字节
fos.write(number >> 16);
fos.write(number >> 8);
fos.write(number); // 写入低位字节

// 写入布尔值转换为字节
boolean flag = true;
fos.write(flag ? 1 : 0);

// 写入字符
char ch = 'X';
fos.write(ch);

// 写入字符串
String text = "Hello";
fos.write(text.getBytes());

} catch (IOException e) {
e.printStackTrace();
}
}

字节数组输入输出流

ByteArrayInputStream () 从内存中的字节数组读取数据它将字节数组包装成输入流可以在不创建临时文件的情况下处理内存中的数据

  • 数据源是内存中的字节数组
  • 不需要关闭流关闭无效但习惯上还是会调用
  • 适合处理小型数据或测试场景
  • 支持标记和重置操作

ByteArrayOutputStream 将数据写入内存中的字节数组它自动管理内部缓冲区随着数据写入而增长最终可以通过 toByteArray() 获取数据

  • 数据写入内存不涉及磁盘
  • 自动扩容的缓冲区
  • 可以获取字节数组或字符串
  • 不需要关闭流关闭无效
  • 适合临时存储或数据传输

⭐⭐ 读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import java.io.*;

public class ByteArrayInputStreamDemo {
public static void main(String[] args) {
// 准备源数据创建一个字节数组
byte[] data = "Hello, ByteArrayInputStream!".getBytes();

// 创建ByteArrayInputStream对象包装字节数组
ByteArrayInputStream bais = new ByteArrayInputStream(data);

try {
int byteData; // 存储读取的字节

System.out.println("方法1逐个字节读取");
// 方法1逐个字节读取
while ((byteData = bais.read()) != -1) {
System.out.print((char) byteData);
}

// 重置流到开头因为上面已经读完需要重置才能重新读取
bais.reset();

System.out.println("\n\n方法2批量读取");
// 方法2批量读取
byte[] buffer = new byte[10]; // 创建10字节的缓冲区
int bytesRead; // 实际读取的字节数

while ((bytesRead = bais.read(buffer)) != -1) {
// 将读取的字节转换为字符串并打印
System.out.print(new String(buffer, 0, bytesRead));
}

// 演示available()方法获取可读取的字节数
// 注意需要重置流
bais.reset();
System.out.println("\n\n可读取的字节数: " + bais.available());

// 演示skip()方法跳过指定字节数
bais.skip(7); // 跳过前7个字节"Hello, "
System.out.print("跳过7个字节后的内容: ");
while ((byteData = bais.read()) != -1) {
System.out.print((char) byteData);
}

} catch (IOException e) {
e.printStackTrace();
}
// ByteArrayInputStream的close()方法实际上什么都不做但为了规范还是调用
try {
bais.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 写入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static void basicDemo() {
// 创建ByteArrayOutputStream默认初始容量32字节
ByteArrayOutputStream baos = new ByteArrayOutputStream();

try {
// 写入单个字节
baos.write(65); // 'A'
baos.write(66); // 'B'
baos.write(67); // 'C'

// 写入字节数组
byte[] bytes = "Hello".getBytes();
baos.write(bytes);

// 写入字节数组的一部分
byte[] more = " World!".getBytes();
baos.write(more, 0, 6); // 写入" World"

// 获取写入的数据
byte[] result = baos.toByteArray();
System.out.println("写入的数据: " + new String(result));

// 获取当前大小
System.out.println("当前大小: " + baos.size() + " 字节");

// 重置缓冲区清空数据
baos.reset();
System.out.println("重置后大小: " + baos.size());

// 写入新数据
baos.write("New Data".getBytes());
System.out.println("新数据: " + baos.toString());
} finally {
try {
baos.close(); // 实际上什么都不做但习惯性调用
} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 指定初始容量减少扩容次数提高性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void specifyCapacityDemo() {
// 指定初始容量为1024字节避免频繁扩容
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);

try {
for (int i = 0; i < 100; i++) {
String data = "Line " + i + "\n";
baos.write(data.getBytes());
}

System.out.println("最终大小: " + baos.size() + " 字节");
System.out.println("缓冲区容量: " + baos.toByteArray().length + " 字节");

} finally {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 写入不同类型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void writeDifferentTypes() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();

try {
// 写入整数需要手动拆分
int num = 123456789;
baos.write((num >> 24) & 0xFF);
baos.write((num >> 16) & 0xFF);
baos.write((num >> 8) & 0xFF);
baos.write(num & 0xFF);

// 写入字符串
baos.write("Hello".getBytes());

// 写入布尔值
baos.write(1); // true

// 获取所有数据
byte[] data = baos.toByteArray();
System.out.println("总数据大小: " + data.length + " 字节");

} finally {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 转换为不同格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void convertToDifferentFormats() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();

try {
baos.write("Java I/O Tutorial".getBytes());

// 转换为字节数组
byte[] bytes = baos.toByteArray();
System.out.println("字节数组: " + bytes.length + " 字节");

// 转换为字符串使用默认编码
String str = baos.toString();
System.out.println("字符串: " + str);

// 转换为指定编码的字符串
String utf8Str = baos.toString("UTF-8");
System.out.println("UTF-8字符串: " + utf8Str);

// 写入到文件
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
baos.writeTo(fos); // 将ByteArrayOutputStream的内容写入文件
System.out.println("已写入文件");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 合并多个流数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void mergeMultipleStreams() {
ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
ByteArrayOutputStream merged = new ByteArrayOutputStream();

try {
// 准备数据
baos1.write("First stream data. ".getBytes());
baos2.write("Second stream data.".getBytes());

// 合并
baos1.writeTo(merged);
baos2.writeTo(merged);

System.out.println("合并后: " + merged.toString());

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
baos1.close();
baos2.close();
merged.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

管道输入输出流

PipedInputStreamPipedOutputStream 配合使用实现线程间的通信一个线程通过 PipedOutputStream 写入数据另一个线程通过 PipedInputStream 读取数据

  • 用于线程间通信
  • 必须成对使用输入流和输出流
  • 数据是先进先出FIFO
  • 线程安全
  • 连接后自动建立管道

⭐⭐ 生产者线程向管道写入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Producer extends Thread {
private PipedOutputStream pos; // 管道输出流

public Producer(PipedOutputStream pos) {
this.pos = pos;
}

@Override
public void run() {
try {
String message = "Hello from Producer!"; // 要发送的消息
byte[] data = message.getBytes(); // 转换为字节数组

System.out.println("生产者开始发送数据...");
pos.write(data); // 向管道写入数据
pos.flush(); // 刷新缓冲区
System.out.println("生产者数据发送完成");

// 发送更多数据
for (int i = 1; i <= 5; i++) {
String msg = "消息 " + i;
pos.write(msg.getBytes());
Thread.sleep(500); // 模拟生产间隔
}

pos.close(); // 关闭输出流通知消费者没有更多数据
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

⭐⭐ 消费者线程从管道读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Consumer extends Thread {
private PipedInputStream pis; // 管道输入流

public Consumer(PipedInputStream pis) {
this.pis = pis;
}

@Override
public void run() {
try {
byte[] buffer = new byte[1024]; // 缓冲区
int bytesRead; // 实际读取的字节数

System.out.println("消费者等待接收数据...");

// 循环读取管道中的数据
while ((bytesRead = pis.read(buffer)) != -1) {
String message = new String(buffer, 0, bytesRead);
System.out.println("消费者接收到: " + message);
}

pis.close(); // 关闭输入流
} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class PipedInputStreamDemo {
public static void main(String[] args) {
try {
// 创建管道流对象
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();

// 连接管道两种方式均可
pis.connect(pos); // 输入流连接输出流
// pos.connect(pis); // 或输出流连接输入流

// 创建生产者和消费者线程
Producer producer = new Producer(pos);
Consumer consumer = new Consumer(pis);

// 启动线程
consumer.start(); // 先启动消费者等待数据
Thread.sleep(1000); // 等待消费者准备好
producer.start(); // 再启动生产者发送数据

// 等待线程结束
producer.join();
consumer.join();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

对象输入输出流

ObjectInputStream 用于反序列化对象即从输入流中读取之前通过 ObjectOutputStream 序列化的 Java 对象它可以将字节流恢复为 Java 对象

  • 反序列化Java对象
  • 要求类实现 Serializable 接口
  • 支持基本类型和对象
  • 必须处理 ClassNotFoundException
  • 可以控制序列化过程transient关键字writeObject/readObject方法

ObjectOutputStream 用于序列化 Java 对象将对象转换为字节流以便存储到文件或通过网络传输只有实现了 Serializable 接口的对象才能被序列化

  • 序列化Java对象
  • 支持基本类型和对象
  • 处理对象引用避免重复序列化
  • 可以控制序列化过程transientwriteObject/readObject
  • 支持版本控制serialVersionUID

⭐⭐ 定义可序列化的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 可序列化的学生类
class Student implements Serializable {
private static final long serialVersionUID = 1L; // 版本号

private String name; // 姓名
private int age; // 年龄
private transient String password; // transient字段不会被序列化
private List<String> courses; // 课程列表可序列化

public Student(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
this.courses = new ArrayList<>();
}

public void addCourse(String course) {
courses.add(course);
}

@Override
public String toString() {
return "Student{name='" + name + "', age=" + age +
", password='" + password + "', courses=" + courses + "}";
}
}

// 自定义序列化的类
class CustomSerialization implements Serializable {
private static final long serialVersionUID = 1L;

private String data;
private transient String sensitiveData; // 敏感数据不序列化

public CustomSerialization(String data, String sensitiveData) {
this.data = data;
this.sensitiveData = sensitiveData;
}

// 自定义序列化方法
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 序列化默认字段
// 对敏感数据进行加密后序列化示例中简单处理
out.writeUTF(encrypt(sensitiveData));
}

// 自定义反序列化方法
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 反序列化默认字段
// 解密敏感数据
this.sensitiveData = decrypt(in.readUTF());
}

private String encrypt(String s) {
// 简单的加密示例实际应使用更强的加密
return new StringBuilder(s).reverse().toString();
}

private String decrypt(String s) {
return new StringBuilder(s).reverse().toString();
}

@Override
public String toString() {
return "CustomSerialization{data='" + data +
"', sensitiveData='" + sensitiveData + "'}";
}
}

⭐⭐ 序列化单个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 序列化对象
public static void serializeSingleObject() {
Student student = new Student("张三", 20, "secret123");
student.addCourse("Java");
student.addCourse("Python");
student.addCourse("Database");

try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("student.dat"))) {

System.out.println("开始序列化对象: " + student);
oos.writeObject(student); // 写入对象
System.out.println("序列化完成");

} catch (IOException e) {
e.printStackTrace();
}
}

// 反序列化对象
public static void deserializeObject() {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("student.dat"))) {

System.out.println("\n开始反序列化对象...");
Student student = (Student) ois.readObject();
System.out.println("反序列化结果: " + student);
// 注意password字段为null因为transient修饰

} catch (FileNotFoundException e) {
System.out.println("文件未找到: " + e.getMessage());
} catch (IOException e) {
System.out.println("I/O错误: " + e.getMessage());
} catch (ClassNotFoundException e) {
System.out.println("类未找到: " + e.getMessage());
}
}

⭐⭐ 序列化多个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public static void serializeMultipleObjects() {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("multiple.dat"))) {

System.out.println("\n序列化多个对象...");

// 写入整数
oos.writeInt(100);
oos.writeUTF("Hello World");

// 写入对象
oos.writeObject(new Student("小明", 18, "xiao123"));
oos.writeObject(new Student("小红", 19, "hong456"));

// 写入布尔值
oos.writeBoolean(true);

System.out.println("多个对象序列化完成");

} catch (IOException e) {
e.printStackTrace();
}

// 按顺序反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("multiple.dat"))) {

System.out.println("\n反序列化多个对象...");

int num = ois.readInt();
String str = ois.readUTF();
Student s1 = (Student) ois.readObject();
Student s2 = (Student) ois.readObject();
boolean flag = ois.readBoolean();

System.out.println("整数: " + num);
System.out.println("字符串: " + str);
System.out.println("学生1: " + s1);
System.out.println("学生2: " + s2);
System.out.println("布尔值: " + flag);

} catch (Exception e) {
e.printStackTrace();
}
}

⭐⭐ 序列化集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public static void serializeCollection() {
List<Student> studentList = new ArrayList<>();

// 创建多个学生对象
studentList.add(new Student("李四", 21, "pass1"));
studentList.add(new Student("王五", 22, "pass2"));
studentList.add(new Student("赵六", 20, "pass3"));

// 为学生添加课程
studentList.get(0).addCourse("Math");
studentList.get(1).addCourse("Physics");
studentList.get(2).addCourse("Chemistry");

try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("students.dat"))) {

System.out.println("\n序列化学生列表...");
oos.writeObject(studentList); // 序列化整个集合
System.out.println("序列化完成共 " + studentList.size() + " 个学生");

} catch (IOException e) {
e.printStackTrace();
}

// 反序列化集合
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("students.dat"))) {

@SuppressWarnings("unchecked")
List<Student> deserializedList = (List<Student>) ois.readObject();
System.out.println("反序列化学生列表共 " + deserializedList.size() + " 个学生");
for (Student s : deserializedList) {
System.out.println(" " + s);
}

} catch (Exception e) {
e.printStackTrace();
}
}

⭐⭐ 反序列化基本类型数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void deserializePrimitives() {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("data.dat"))) {

// 按照写入的顺序读取
int num = ois.readInt(); // 读取整数
String str = ois.readUTF(); // 读取UTF字符串
double d = ois.readDouble(); // 读取双精度浮点数

System.out.println("读取整数: " + num);
System.out.println("读取字符串: " + str);
System.out.println("读取浮点数: " + d);

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 自定义序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void customSerializationDemo() {
CustomSerialization cs = new CustomSerialization("Public Data", "Sensitive Password");

try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("custom.dat"))) {

System.out.println("\n自定义序列化...");
oos.writeObject(cs);
System.out.println("原始对象: " + cs);

} catch (IOException e) {
e.printStackTrace();
}

try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("custom.dat"))) {

CustomSerialization deserialized = (CustomSerialization) ois.readObject();
System.out.println("反序列化对象: " + deserialized);

} catch (Exception e) {
e.printStackTrace();
}
}

合并输入流

SequenceInputStream 将多个输入流合并成一个逻辑流当读取时它会依次从第一个流读取直到第一个流结束然后自动切换到第二个流以此类推

  • 合并多个输入流
  • 可以处理任意数量的流
  • 对调用者透明看起来像一个流
  • 适合合并多个文件

⭐⭐ 合并两个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void mergeTwoFiles() {
try {
// 创建两个文件输入流
FileInputStream fis1 = new FileInputStream("file1.txt");
FileInputStream fis2 = new FileInputStream("file2.txt");

// 创建SequenceInputStream合并两个流
SequenceInputStream sis = new SequenceInputStream(fis1, fis2);

// 读取合并后的数据
System.out.println("=== 合并文件1和文件2 ===");
byte[] buffer = new byte[1024];
int bytesRead;

while ((bytesRead = sis.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, bytesRead));
}

sis.close(); // 关闭合并流会自动关闭fis1和fis2

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 合并多个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static void mergeMultipleFiles() {
try {
// 准备多个文件输入流
Vector<FileInputStream> streams = new Vector<>();

// 添加三个文件流
streams.add(new FileInputStream("file1.txt"));
streams.add(new FileInputStream("file2.txt"));
streams.add(new FileInputStream("file3.txt"));

// 获取Vector的枚举器
// SequenceInputStream接受Enumeration作为参数
java.util.Enumeration<FileInputStream> enumeration = streams.elements();

// 创建SequenceInputStream合并多个流
SequenceInputStream sis = new SequenceInputStream(enumeration);

// 读取合并后的数据
System.out.println("\n=== 合并多个文件 ===");
byte[] buffer = new byte[1024];
int bytesRead;

while ((bytesRead = sis.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, bytesRead));
}

sis.close(); // 关闭合并流会自动关闭所有文件流

} catch (IOException e) {
e.printStackTrace();
}
}

音频输入流

AudioInputStream 位于 javax.sound.sampled 包中专门用于处理音频数据它提供了音频格式信息采样率位深度声道数等并支持从各种来源读取音频数据

处理音频格式信息
支持多种音频格式WAVAIFFAU等
可以获取音频格式信息
支持音频格式转换
可以处理音频文件流

⭐⭐ 读取音频文件信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void readAudioInfo() throws Exception {
// 获取音频输入流
try (AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(
new File("music.wav"))) {

// 获取音频格式信息
AudioFormat format = audioInputStream.getFormat();

System.out.println("=== 音频文件信息 ===");
System.out.println("文件: " + audioFile);
System.out.println("编码格式: " + format.getEncoding());
System.out.println("采样率: " + format.getSampleRate() + " Hz");
System.out.println("采样位数: " + format.getSampleSizeInBits() + " bits");
System.out.println("声道数: " + format.getChannels());
System.out.println("帧率: " + format.getFrameRate() + " fps");
System.out.println("帧大小: " + format.getFrameSize() + " bytes");
System.out.println("是否大端序: " + format.isBigEndian());

// 获取音频长度
long frameLength = audioInputStream.getFrameLength();
System.out.println("总帧数: " + frameLength);

// 计算音频时长
double duration = frameLength / format.getFrameRate();
System.out.println("时长: " + duration + " 秒");

// 获取可读取的字节数
int available = audioInputStream.available();
System.out.println("可读取字节数: " + available);
}
}

⭐⭐ 播放音频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public static void playAudio() throws Exception {
// 获取音频输入流
try (AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(
new File("music.wav"))) {

// 获取音频格式
AudioFormat format = audioInputStream.getFormat();

// 创建DataLine.Info对象用于获取源数据线
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

// 检查是否支持该格式
if (!AudioSystem.isLineSupported(info)) {
System.out.println("不支持该音频格式播放");
return;
}

// 获取源数据线
try (SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info)) {
// 打开数据线
line.open(format);
// 开始播放
line.start();

System.out.println("开始播放: " + audioFile);

// 缓冲区大小
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesRead;

// 从音频流读取数据并写入数据线
while ((bytesRead = audioInputStream.read(buffer, 0, bufferSize)) != -1) {
line.write(buffer, 0, bytesRead);
}

// 等待所有数据播放完成
line.drain();
System.out.println("播放完成");
}
}
}

⭐⭐ 转换音频格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public static void convertAudioFormat() throws Exception {
// 获取原始音频输入流
try (AudioInputStream originalStream = AudioSystem.getAudioInputStream(
new File("music.wav"))) {

// 原始格式
AudioFormat originalFormat = originalStream.getFormat();

// 目标格式转换为16位44100Hz立体声
AudioFormat targetFormat = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED, // 编码格式有符号PCM
44100.0f, // 采样率44.1kHz
16, // 采样位数16位
2, // 声道数立体声
4, // 帧大小2声道 * 2字节 = 4字节
44100.0f, // 帧率
false // 小端序
);

// 转换音频格式
AudioFormat[] formats = {targetFormat};
AudioInputStream convertedStream = AudioSystem.getAudioInputStream(
targetFormat, originalStream);

// 写入转换后的音频文件
AudioSystem.write(convertedStream, AudioFileFormat.Type.WAVE, new File("converted.wav"));

System.out.println("音频格式转换完成");
System.out.println("原始格式: " + originalFormat);
System.out.println("目标格式: " + targetFormat);

convertedStream.close();
}
}

⭐⭐ 读取音频数据到字节数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static byte[] readAudioData() throws Exception {
try (AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(
new File("music.wav"))) {

// 获取音频长度
int frameLength = (int) audioInputStream.getFrameLength();
int frameSize = audioInputStream.getFormat().getFrameSize();
int dataSize = frameLength * frameSize;

// 创建字节数组存储音频数据
byte[] audioData = new byte[dataSize];

// 读取所有音频数据
int bytesRead = audioInputStream.read(audioData);
System.out.println("读取音频数据: " + bytesRead + " 字节");

return audioData;
}
}

⭐⭐ 从 URL 读取音频

1
2
3
4
5
6
7
8
public static void playFromURL(String audioUrl) throws Exception {
java.net.URL url = new java.net.URL(audioUrl);
try (AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(url)) {
// 播放逻辑与上面相同
System.out.println("从URL播放: " + audioUrl);
// ... 播放代码
}
}

⭐⭐ 录制音频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public static void recordAudio() throws Exception {
// 定义录制格式
AudioFormat format = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED, // 编码格式
44100.0f, // 采样率
16, // 采样位数
2, // 声道数
4, // 帧大小
44100.0f, // 帧率
false // 字节序
);

// 获取目标数据线
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);

if (!AudioSystem.isLineSupported(info)) {
System.out.println("不支持录制");
return;
}

// 获取并打开目标数据线
TargetDataLine line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format);
line.start();

System.out.println("开始录制...");

// 创建AudioInputStream
AudioInputStream recordingStream = new AudioInputStream(line);

// 录制5秒
Thread.sleep(5000);

// 停止录制
line.stop();
line.close();

// 保存录制的内容
AudioSystem.write(recordingStream, AudioFileFormat.Type.WAVE, new File("recording.wav"));

System.out.println("录制完成已保存到 recording.wav");
recordingStream.close();
}

FilterInputStream

FilterInputStream 是所有过滤输入流的基类它包装了另一个输入流可以在读取数据时添加额外的功能它本身是一个抽象类提供了装饰器模式的基础

  • 包装其他输入流增强功能
  • 可以嵌套使用多个过滤器
  • 子类实现具体功能

FilterOutputStream

FilterOutputStream 是所有过滤输出流的基类它包装了另一个输出流可以在写入数据时添加额外的功能它本身是一个抽象类提供了装饰器模式的基础

  • 包装其他输出流增强功能
  • 可以嵌套使用多个过滤器
  • 子类实现具体功能

缓冲输入输出流

BufferedInputStream 为输入流提供缓冲功能它内部维护一个字节数组作为缓冲区减少底层系统调用的次数从而提高I/O性能

  • 提高读取性能减少系统调用
  • 支持标记和重置mark/reset
  • 可以设置缓冲区大小

BufferedOutputStream 为输出流提供缓冲功能它内部维护一个字节数组作为缓冲区减少底层系统调用的次数从而提高I/O性能

  • 提高写入性能减少系统调用
  • 可以设置缓冲区大小
  • 需要调用flush()确保数据写入

⭐⭐ 使用缓冲的读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void readWithBuffer() {
long startTime = System.currentTimeMillis();

try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("largefile.txt"), 8192)) { // 设置缓冲区大小8KB

int data;
// 实际读取时BufferedInputStream会批量读取到内部缓冲区
// 减少系统调用次数提高性能
while ((data = bis.read()) != -1) {
// 处理数据这里只是读取
}

long endTime = System.currentTimeMillis();
System.out.println("使用缓冲耗时: " + (endTime - startTime) + "ms");

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 标记和重置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static void markAndResetDemo() {
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("example.txt"))) {

// markSupported()检查是否支持标记
if (bis.markSupported()) {
System.out.println("支持mark/reset操作");

// 读取前10个字节
byte[] buffer = new byte[10];
bis.read(buffer);
System.out.println("前10个字节: " + new String(buffer));

// 标记当前位置参数是readlimit标记有效期内最多读取的字节数
bis.mark(100);

// 继续读取20个字节
byte[] next20 = new byte[20];
bis.read(next20);
System.out.println("接下来的20个字节: " + new String(next20));

// 重置到标记位置
bis.reset();
System.out.println("已重置到标记位置");

// 重新读取标记后的内容
byte[] afterReset = new byte[20];
bis.read(afterReset);
System.out.println("重置后读取的内容: " + new String(afterReset));
}

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 写入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void basicUsage() {
// 默认缓冲区大小通常为8192字节
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("basic.txt"))) {

// 写入数据到缓冲区
bos.write("Hello".getBytes());
bos.write(" World".getBytes());

// 重要刷新缓冲区确保数据真正写入文件
bos.flush();

System.out.println("基本缓冲写入完成");

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 设置自定义缓冲区大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void setBufferSize() {
// 设置缓冲区大小为4096字节4KB
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("customBuffer.txt"), 4096)) {

byte[] data = new byte[1024]; // 1KB数据
// 填充数据
for (int i = 0; i < data.length; i++) {
data[i] = (byte) ('A' + (i % 26));
}

// 写入10次每次1KB共10KB
for (int i = 0; i < 10; i++) {
bos.write(data);
}

bos.flush();
System.out.println("使用4KB缓冲区写入完成");

} catch (IOException e) {
e.printStackTrace();
}
}

数据输入输出流

DataInputStream 允许应用程序以与机器无关的方式从底层输入流中读取基本Java数据类型它提供了读取各种类型数据的方法

  • 读取基本数据类型和字符串
  • 与 DataOutputStream 配合使用
  • 平台无关使用固定的字节格式
  • 可以读取 UTF-8 编码的字符串

DataOutputStream 允许应用程序以与机器无关的方式将基本 Java 数据类型写入底层输出流它提供了写入各种类型数据的方法

  • 写入基本数据类型和字符串
  • 与 DataInputStream 配合使用
  • 平台无关使用固定的字节格式
  • 可以写入UTF-8编码的字符串

⭐⭐ 写入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void writeData() {
try (DataOutputStream dos = new DataOutputStream(
new FileOutputStream("data.bin"))) {

// 写入不同类型的数据
dos.writeInt(100); // 写入整数
dos.writeDouble(3.14159); // 写入双精度浮点数
dos.writeBoolean(true); // 写入布尔值
dos.writeChar('A'); // 写入字符
dos.writeUTF("Hello, DataInputStream!"); // 写入UTF字符串

System.out.println("数据写入完成");

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void readData() {
try (DataInputStream dis = new DataInputStream(
new FileInputStream("data.bin"))) {

// 必须按照写入的顺序读取
int intValue = dis.readInt(); // 读取整数
double doubleValue = dis.readDouble(); // 读取双精度浮点数
boolean boolValue = dis.readBoolean(); // 读取布尔值
char charValue = dis.readChar(); // 读取字符
String strValue = dis.readUTF(); // 读取UTF字符串

// 打印读取的数据
System.out.println("读取整数: " + intValue);
System.out.println("读取浮点数: " + doubleValue);
System.out.println("读取布尔值: " + boolValue);
System.out.println("读取字符: " + charValue);
System.out.println("读取字符串: " + strValue);

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 写入数组和集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public static void writeArrayAndCollection() {
try (DataOutputStream dos = new DataOutputStream(
new FileOutputStream("array.dat"))) {

// 写入整数数组
int[] numbers = {10, 20, 30, 40, 50};

// 先写入数组长度
dos.writeInt(numbers.length);

// 再写入数组元素
for (int num : numbers) {
dos.writeInt(num);
}

// 写入字符串数组
String[] names = {"Apple", "Banana", "Cherry"};
dos.writeInt(names.length);
for (String name : names) {
dos.writeUTF(name);
}

System.out.println("\n数组数据写入完成");

} catch (IOException e) {
e.printStackTrace();
}

// 读取数组数据
try (DataInputStream dis = new DataInputStream(
new FileInputStream("array.dat"))) {

// 读取整数数组
int length = dis.readInt();
int[] numbers = new int[length];
for (int i = 0; i < length; i++) {
numbers[i] = dis.readInt();
}

System.out.print("整数数组: ");
for (int num : numbers) {
System.out.print(num + " ");
}
System.out.println();

// 读取字符串数组
length = dis.readInt();
String[] names = new String[length];
for (int i = 0; i < length; i++) {
names[i] = dis.readUTF();
}

System.out.print("字符串数组: ");
for (String name : names) {
System.out.print(name + " ");
}
System.out.println();

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 大小端序Java使用大端序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void endiannessDemo() {
try (DataOutputStream dos = new DataOutputStream(
new FileOutputStream("endian.dat"))) {

int value = 0x12345678; // 十六进制值

System.out.println("\n大小端序演示:");
System.out.printf("原始值: 0x%08X\n", value);

dos.writeInt(value);

// 读取原始字节查看顺序
try (FileInputStream fis = new FileInputStream("endian.dat")) {
System.out.print("存储的字节顺序: ");
for (int i = 0; i < 4; i++) {
int b = fis.read();
System.out.printf("0x%02X ", b);
}
System.out.println("\nJava使用大端序高位字节存储在低地址");
}

} catch (IOException e) {
e.printStackTrace();
}
}

加密输入输出流

CipherInputStream 位于 javax.crypto 包中用于对输入流进行加密或解密它包装了一个 Cipher 对象在读取数据时自动进行加解密处理

  • 透明地进行加解密操作
  • 支持多种加密算法AESDESRSA等
  • 需要配合 Cipher 对象使用
  • 适合处理加密文件或网络传输

CipherOutputStream 位于 javax.crypto 包中用于对输出流进行加密或解密它包装了一个 Cipher 对象在写入数据时自动进行加解密处理

  • 透明地进行加解密操作
  • 支持多种加密算法AESDESRSA等
  • 需要配合 Cipher 对象使用
  • 适合加密文件或网络传输

⭐⭐ 加密解密文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public static void main(String[] args) {
try {
// 生成加密密钥
SecretKey secretKey = generateKey();

// 加密文件
encryptFile("plaintext.txt", "encrypted.dat", secretKey);

// 解密文件
decryptFile("encrypted.dat", "decrypted.txt", secretKey);

} catch (Exception e) {
e.printStackTrace();
}
}

// 生成AES密钥
public static SecretKey generateKey() throws NoSuchAlgorithmException {
// 创建密钥生成器使用AES算法
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128); // 初始化密钥长度为128位
SecretKey secretKey = keyGen.generateKey(); // 生成密钥
return secretKey;
}

// 加密文件
public static void encryptFile(String inputFile, String outputFile, SecretKey key) throws Exception {
// 创建Cipher对象指定算法和模式
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

// 生成初始化向量IV
byte[] iv = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);

// 初始化Cipher为加密模式
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

// 创建文件输入流和输出流
try (FileInputStream fis = new FileInputStream(inputFile);
FileOutputStream fos = new FileOutputStream(outputFile);
CipherInputStream cis = new CipherInputStream(fis, cipher)) {

// 先写入IV到文件解密时需要
fos.write(iv);

// 加密并写入数据
byte[] buffer = new byte[1024]; // 缓冲区
int bytesRead; // 实际读取的字节数

// 从CipherInputStream读取已加密的数据
while ((bytesRead = cis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead); // 写入加密后的数据
}

System.out.println("文件加密完成: " + outputFile);
}
}

// 解密文件
public static void decryptFile(String inputFile, String outputFile, SecretKey key) throws Exception {
// 创建Cipher对象
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

try (FileInputStream fis = new FileInputStream(inputFile);
FileOutputStream fos = new FileOutputStream(outputFile)) {

// 读取IV与加密时写入的顺序一致
byte[] iv = new byte[16];
fis.read(iv); // 读取初始化向量
IvParameterSpec ivSpec = new IvParameterSpec(iv);

// 初始化Cipher为解密模式
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

// 创建CipherInputStream包装文件输入流
try (CipherInputStream cis = new CipherInputStream(fis, cipher)) {

byte[] buffer = new byte[1024]; // 缓冲区
int bytesRead; // 实际读取的字节数

// 从CipherInputStream读取解密后的数据
while ((bytesRead = cis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead); // 写入解密后的数据
}

System.out.println("文件解密完成: " + outputFile);
}
}
}

⭐⭐ 加密解密内存中的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 加密内存中的数据
public static byte[] encryptData(byte[] data, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] iv = new byte[12];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (CipherOutputStream cos = new CipherOutputStream(baos, cipher)) {
cos.write(data);
}

byte[] encryptedData = baos.toByteArray();
// 将IV和加密数据合并
byte[] result = new byte[iv.length + encryptedData.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(encryptedData, 0, result, iv.length, encryptedData.length);

return result;
}

// 解密内存中的数据
public static byte[] decryptData(byte[] encryptedData, SecretKey key)
throws Exception {
// 提取IV
byte[] iv = new byte[12];
System.arraycopy(encryptedData, 0, iv, 0, iv.length);

// 提取加密数据
byte[] cipherText = new byte[encryptedData.length - iv.length];
System.arraycopy(encryptedData, iv.length, cipherText, 0, cipherText.length);

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);

ByteArrayInputStream bais = new ByteArrayInputStream(cipherText);
ByteArrayOutputStream baos = new ByteArrayOutputStream();

try (CipherInputStream cis = new CipherInputStream(bais, cipher)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = cis.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
}

return baos.toByteArray();
}

⭐⭐ 使用自定义密码的加密文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void encryptWithPassword(String inputFile, String outputFile, String password) throws Exception {
// 从密码生成密钥
byte[] keyBytes = password.getBytes("UTF-8");
// 使用SHA-256对密码进行哈希确保密钥长度足够
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(keyBytes);
// 使用前16字节作为AES-128密钥
SecretKeySpec keySpec = new SecretKeySpec(hash, 0, 16, "AES");

// 创建Cipher并执行加密与上面类似
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

try (FileInputStream fis = new FileInputStream(inputFile);
FileOutputStream fos = new FileOutputStream(outputFile);
CipherInputStream cis = new CipherInputStream(fis, cipher)) {

fos.write(iv); // 写入IV
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = cis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}

System.out.println("使用密码加密完成");
}
}

⭐⭐ 使用密码加密从密码派生密钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static void encryptWithPassword(String inputFile, String outputFile, String password) throws Exception {
// 从密码派生密钥
byte[] salt = new byte[8];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);

// 使用PBKDF2从密码派生密钥
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec keySpec = new SecretKeySpec(tmp.getEncoded(), "AES");

// 创建Cipher
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16];
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

try (FileInputStream fis = new FileInputStream(inputFile);
FileOutputStream fos = new FileOutputStream(outputFile);
CipherOutputStream cos = new CipherOutputStream(fos, cipher)) {

// 写入salt和IV
fos.write(salt);
fos.write(iv);

// 加密数据
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
cos.write(buffer, 0, bytesRead);
}

System.out.println("使用密码加密完成: " + outputFile);
}
}

⭐⭐ 加密大文件流式处理内存友好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public static void encryptLargeFile(String inputFile, String outputFile, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

long startTime = System.currentTimeMillis();

try (FileInputStream fis = new FileInputStream(inputFile);
FileOutputStream fos = new FileOutputStream(outputFile);
CipherOutputStream cos = new CipherOutputStream(fos, cipher)) {

// 写入IV
fos.write(iv);

// 使用大缓冲区处理大文件
byte[] buffer = new byte[65536]; // 64KB缓冲区
int bytesRead;
long totalBytes = 0;

while ((bytesRead = fis.read(buffer)) != -1) {
cos.write(buffer, 0, bytesRead);
totalBytes += bytesRead;

// 每10MB打印一次进度
if (totalBytes % (10 * 1024 * 1024) < bytesRead) {
System.out.printf("已加密: %.2f MB%n", totalBytes / (1024.0 * 1024.0));
}
}

long endTime = System.currentTimeMillis();
System.out.printf("大文件加密完成: %s (%.2f MB, 耗时: %.2f秒)%n",
outputFile, totalBytes / (1024.0 * 1024.0),
(endTime - startTime) / 1000.0);

} catch (IOException e) {
e.printStackTrace();
}
}

回推输入流

PushbackInputStream 允许将读取的字节推回到流中以便后续再次读取这在解析数据时非常有用当需要”预览”下一个字节而不实际消费它时

  • 支持回推已读取的字节
  • 可以设置回推缓冲区大小
  • 适合解析器编译器等场景
  • 回推的字节会被后续的read方法重新读取

⭐⭐ 回推数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static void basicPushbackDemo() {
try (PushbackInputStream pbis = new PushbackInputStream(
new FileInputStream("example.txt"), 10)) { // 设置回推缓冲区大小

System.out.println("=== 基本回推演示 ===");

// 读取第一个字节
int firstByte = pbis.read();
System.out.println("读取到: " + (char) firstByte);

// 将刚才读取的字节推回流中
pbis.unread(firstByte);
System.out.println("已将字节推回流中");

// 再次读取会重新得到刚才的字节
int reReadByte = pbis.read();
System.out.println("重新读取到: " + (char) reReadByte);

// 批量回推多个字节
byte[] bytesToPush = "ABC".getBytes();
pbis.unread(bytesToPush);
System.out.println("已回推3个字节: A, B, C");

// 读取回推的字节
System.out.print("读取回推的字节: ");
for (int i = 0; i < 3; i++) {
int b = pbis.read();
System.out.print((char) b);
}
System.out.println();

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 查找并处理特殊标记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static void parseWithPushback() {
try (PushbackInputStream pbis = new PushbackInputStream(
new FileInputStream("config.txt"))) {

System.out.println("\n=== 解析配置文件 ===");

int data;
while ((data = pbis.read()) != -1) {
// 检查是否遇到注释标记 '#'
if (data == '#') {
System.out.println("发现注释跳过整行...");
// 跳过整行注释
while ((data = pbis.read()) != -1 && data != '\n') {
// 跳过注释内容
}
} else {
// 读取有效内容直到行尾
StringBuilder line = new StringBuilder();
while (data != -1 && data != '\n') {
line.append((char) data);
data = pbis.read();
}
if (line.length() > 0) {
System.out.println("有效内容: " + line.toString());
}
}
}

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 读取数字向前查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void peekDemo() {
try (PushbackInputStream pbis = new PushbackInputStream(
new FileInputStream("numbers.txt"))) {
int data;
while ((data = pbis.read()) != -1) {
// 检查下一个字节是否是数字
int nextByte = pbis.read();
if (nextByte != -1) {
// 将下一个字节推回去
pbis.unread(nextByte);

if (Character.isDigit((char) data) &&
Character.isDigit((char) nextByte)) {
// 如果当前和下一个都是数字表示是两位数
int twoDigit = (data - '0') * 10 + (nextByte - '0');
System.out.println("读取到两位数: " + twoDigit);
pbis.read(); // 消费掉下一个字节
} else {
System.out.println("读取到单数字: " + (data - '0'));
}
} else {
System.out.println("读取到单数字: " + (data - '0'));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}

打印流

PrintStream 提供了方便的打印方法printprintln可以将各种数据类型格式化为文本输出它是 System.outSystem.err 的类型也是日志输出的常用类

  • 提供 printprintlnprintf 等便捷方法
  • 自动刷新可选
  • 不会抛出 IOException
  • 支持格式化输出
  • 可以自动刷新

⭐⭐ 打印数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void basicPrinting() {
// 创建PrintStream写入文件
try (PrintStream ps = new PrintStream("print.txt")) {

// print方法不换行
ps.print("Hello");
ps.print(" ");
ps.print("World");

// println方法换行
ps.println(); // 空行
ps.println("This is a new line");

// 打印各种类型
ps.println("String: " + "Java");
ps.println("Integer: " + 100);
ps.println("Double: " + 3.14159);
ps.println("Boolean: " + true);
ps.println("Character: " + 'A');
ps.println("Object: " + new Date());

// 打印数组
int[] numbers = {1, 2, 3, 4, 5};
ps.println("Array: " + java.util.Arrays.toString(numbers));

System.out.println("数据已写入print.txt");

} catch (FileNotFoundException e) {
e.printStackTrace();
}
}

⭐⭐ 格式化输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void formattedOutput() {
try (PrintStream ps = new PrintStream("format.txt")) {

// printf方法格式化输出
ps.printf("Hello, %s!%n", "World"); // %n是平台独立的换行符
ps.printf("整数: %d, 浮点数: %.2f%n", 100, 3.14159);

// 格式化表格
ps.println("\n学生成绩表:");
ps.printf("%-10s %-10s %-10s%n", "姓名", "年龄", "成绩");
ps.printf("%-10s %-10d %-10.2f%n", "张三", 20, 85.5);
ps.printf("%-10s %-10d %-10.2f%n", "李四", 21, 92.0);
ps.printf("%-10s %-10d %-10.2f%n", "王五", 19, 78.5);

// 格式化日期
Date now = new Date();
ps.printf("当前时间: %tF %tT%n", now, now);

// 格式化数字
ps.printf("千分位: %,d%n", 1234567);
ps.printf("科学计数法: %e%n", 12345.6789);

System.out.println("格式化数据已写入format.txt");

} catch (FileNotFoundException e) {
e.printStackTrace();
}
}

⭐⭐ 自动刷新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void autoFlushDemo() {
// 创建自动刷新的PrintStream
try (PrintStream ps = new PrintStream(
new FileOutputStream("autoflush.txt"), true)) { // true表示自动刷新

// 当使用printlnprintf或format方法时会自动刷新
ps.println("这行会自动刷新到文件");
ps.printf("自动刷新: %d%n", 100);

// print方法不会自动刷新
ps.print("这行不会自动刷新");
// 需要手动flush或使用println

System.out.println("自动刷新模式已启用");

} catch (IOException e) {
e.printStackTrace();
}

// 不自动刷新
try (PrintStream ps = new PrintStream(
new FileOutputStream("noauto.txt"))) {

ps.print("这行不会自动刷新");
// 需要显式调用flush
ps.flush();

System.out.println("手动刷新模式");

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 重定向 System.out

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void redirectSystemOut() {
// 保存原始System.out
PrintStream originalOut = System.out;

try {
// 创建文件输出流
PrintStream fileOut = new PrintStream("redirect.txt");

// 重定向System.out到文件
System.setOut(fileOut);

// 这些输出会写入文件而不是控制台
System.out.println("这条消息写入文件");
System.out.println("重定向System.out到文件");
System.out.printf("当前时间: %tF %<tT%n", new Date());

// 恢复原始System.out
System.setOut(originalOut);

System.out.println("已恢复控制台输出");
System.out.println("消息已重定向到redirect.txt");

} catch (FileNotFoundException e) {
e.printStackTrace();
// 恢复原始System.out
System.setOut(originalOut);
}
}

⭐⭐ 自定义 PrintStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void customPrintStream() {
// 创建自定义PrintStream添加时间戳
try (PrintStream ps = new PrintStream("custom.txt") {
@Override
public void println(String x) {
// 添加时间戳前缀
String timestamp = new java.text.SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss").format(new Date());
super.println("[" + timestamp + "] " + x);
}

@Override
public void println(int x) {
String timestamp = new java.text.SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss").format(new Date());
super.println("[" + timestamp + "] " + x);
}
}) {

ps.println("第一条日志消息");
Thread.sleep(1000);
ps.println("第二条日志消息");
Thread.sleep(1000);
ps.println(12345);

System.out.println("带时间戳的日志已写入custom.txt");

} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}

⭐⭐ 日志记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public static void loggingExample() {
try (PrintStream log = new PrintStream(
new BufferedOutputStream(new FileOutputStream("app.log")), true)) {

// 日志级别
String[] levels = {"INFO", "WARNING", "ERROR", "DEBUG"};
String[] messages = {
"Application started",
"Configuration loaded successfully",
"Database connection established",
"User logged in: admin",
"Warning: Low disk space",
"Error: File not found",
"Processing request #12345",
"Application shutdown"
};

for (int i = 0; i < messages.length; i++) {
String level = levels[i % levels.length];
String timestamp = new java.text.SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss.SSS").format(new Date());

// 格式化日志条目
log.printf("[%s] [%s] %s%n", timestamp, level, messages[i]);

// 模拟日志产生间隔
Thread.sleep(100);
}

System.out.println("日志已写入app.log");

} catch (IOException | InterruptedException e) {
e.printStackTrace();
}

// 显示日志内容
System.out.println("\n日志内容预览:");
try (BufferedReader br = new BufferedReader(new FileReader("app.log"))) {
String line;
int count = 0;
while ((line = br.readLine()) != null && count < 5) {
System.out.println(line);
count++;
}
System.out.println("...");
} catch (IOException e) {
e.printStackTrace();
}
}

字符流

以字符16 位 Unicode 为单位进行数据读写的流自动处理字符编码如 UTF-8GBKReader输入Writer输出

  • 专门处理文本数据
  • 内部使用字符编码集Charset在字节与字符之间转换

Reader

Reader 是所有字符输入流的抽象基类定义了读取字符数据的基本方法它处理的是16位Unicode字符而不是字节

⭐⭐ 核心方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class Reader implements Readable, Closeable {
// 核心读取方法
public int read() throws IOException // 读取单个字符
public int read(char[] cbuf) throws IOException // 读取到字符数组
public abstract int read(char[] cbuf, int off, int len) // 读取到指定位置
public int read(CharBuffer target) throws IOException // 读取到CharBuffer

// 辅助方法
public long skip(long n) throws IOException // 跳过字符
public boolean ready() throws IOException // 判断是否可读
public boolean markSupported() // 是否支持标记
public void mark(int readAheadLimit) throws IOException // 标记位置
public void reset() throws IOException // 重置到标记
public abstract void close() throws IOException // 关闭流
}

Writer

Writer 是所有字符输出流的抽象基类定义了向目的地写入字符数据的基本方法它处理的是16位Unicode字符而不是字节

⭐⭐ 核心方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class Writer implements Appendable, Closeable, Flushable {
// 核心写入方法
public void write(int c) throws IOException // 写入单个字符
public void write(char[] cbuf) throws IOException // 写入字符数组
public abstract void write(char[] cbuf, int off, int len) throws IOException // 写入指定部分
public void write(String str) throws IOException // 写入字符串
public void write(String str, int off, int len) throws IOException // 写入字符串指定部分

// Appendable接口方法
public Writer append(char c) throws IOException // 追加字符
public Writer append(CharSequence csq) throws IOException // 追加字符序列
public Writer append(CharSequence csq, int start, int end) throws IOException // 追加部分

// 刷新和关闭
public abstract void flush() throws IOException // 刷新缓冲区
public abstract void close() throws IOException // 关闭流
}

字节字符转换流

InputStreamReader 是字节流到字符流的桥梁它读取字节并使用指定的字符集解码为字符

OutputStreamWriter 是字符流到字节流的桥梁它将写入的字符使用指定的字符集编码为字节然后写入底层字节输出流

  • 处理字符编码转换
  • 可以指定字符集UTF-8GBKISO-8859-1等
  • 缓冲字节流以提高性能

⭐⭐ 创建不同编码的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static void createTestFiles() {
String content = "你好世界Hello, World! こんにちは";

// UTF-8
try (FileOutputStream fos = new FileOutputStream("test_utf8.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8")) {
osw.write(content);
} catch (IOException e) {
e.printStackTrace();
}

// GBK
try (FileOutputStream fos = new FileOutputStream("test_gbk.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK")) {
osw.write(content);
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 读取不同字符集的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void basicUsage() {
// 读取UTF-8文件
System.out.println("=== UTF-8 文件读取 ===");
try (FileInputStream fis = new FileInputStream("test_utf8.txt");
InputStreamReader isr = new InputStreamReader(fis, "UTF-8")) {

char[] buffer = new char[1024];
int length = isr.read(buffer);
System.out.println("内容: " + new String(buffer, 0, length));

} catch (IOException e) {
e.printStackTrace();
}

// 读取GBK文件
System.out.println("\n=== GBK 文件读取 ===");
try (FileInputStream fis = new FileInputStream("test_gbk.txt");
InputStreamReader isr = new InputStreamReader(fis, "GBK")) {

char[] buffer = new char[1024];
int length = isr.read(buffer);
System.out.println("内容: " + new String(buffer, 0, length));

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 自动检测并处理BOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public static void detectBOM() {
// 创建带BOM的UTF-8文件
try (FileOutputStream fos = new FileOutputStream("utf8_bom.txt")) {
// 写入UTF-8 BOM: EF BB BF
fos.write(new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF});
fos.write("带BOM的UTF-8文件".getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}

// 智能读取处理BOM
try (FileInputStream fis = new FileInputStream("utf8_bom.txt")) {
// 检测BOM
PushbackInputStream pbis = new PushbackInputStream(fis, 3);
byte[] bom = new byte[3];
int bytesRead = pbis.read(bom);

String encoding = "UTF-8";
int skipBytes = 0;

// 检查BOM
if (bytesRead >= 3 && bom[0] == (byte) 0xEF &&
bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF) {
// UTF-8 BOM detected
skipBytes = 3;
System.out.println("检测到UTF-8 BOM");
} else {
// 没有BOM推回流中
pbis.unread(bom, 0, bytesRead);
}

// 跳过BOM
if (skipBytes > 0) {
pbis.skip(skipBytes);
}

// 使用检测到的编码读取
try (InputStreamReader isr = new InputStreamReader(pbis, encoding)) {
char[] buffer = new char[1024];
int length = isr.read(buffer);
System.out.println("内容: " + new String(buffer, 0, length));
}

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 读取多种编码的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static void readMultipleEncodings() {
Map<String, String> files = new HashMap<>();
files.put("utf8.txt", "UTF-8");
files.put("gbk.txt", "GBK");
files.put("utf16be.txt", "UTF-16BE");
files.put("utf16le.txt", "UTF-16LE");

// 创建测试文件
for (Map.Entry<String, String> entry : files.entrySet()) {
try (FileOutputStream fos = new FileOutputStream(entry.getKey());
OutputStreamWriter osw = new OutputStreamWriter(fos, entry.getValue())) {
osw.write("测试文本 Test Text");
} catch (IOException e) {
e.printStackTrace();
}
}

// 读取所有文件
for (Map.Entry<String, String> entry : files.entrySet()) {
System.out.println("\n读取 " + entry.getKey() + " (" + entry.getValue() + "):");
try (FileInputStream fis = new FileInputStream(entry.getKey());
InputStreamReader isr = new InputStreamReader(fis, entry.getValue())) {

char[] buffer = new char[1024];
int length = isr.read(buffer);
System.out.println(" " + new String(buffer, 0, length));

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 编码转换将一种编码的文件转换为另一种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public static void encodingConversion() {
String sourceFile = "source_gbk.txt";
String targetFile = "target_utf8.txt";

// 创建GBK源文件
try (FileOutputStream fos = new FileOutputStream(sourceFile);
OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK")) {
osw.write("编码转换示例将GBK转换为UTF-8");
} catch (IOException e) {
e.printStackTrace();
}

// 执行转换
try (FileInputStream fis = new FileInputStream(sourceFile);
InputStreamReader isr = new InputStreamReader(fis, "GBK");
FileOutputStream fos = new FileOutputStream(targetFile);
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8")) {

char[] buffer = new char[4096];
int bytesRead;

while ((bytesRead = isr.read(buffer)) != -1) {
osw.write(buffer, 0, bytesRead);
}

System.out.println("编码转换完成: GBK -> UTF-8");

} catch (IOException e) {
e.printStackTrace();
}

// 验证转换结果
try (FileInputStream fis = new FileInputStream(targetFile);
InputStreamReader isr = new InputStreamReader(fis, "UTF-8")) {

char[] buffer = new char[1024];
int length = isr.read(buffer);
System.out.println("转换后内容: " + new String(buffer, 0, length));

} catch (IOException e) {
e.printStackTrace();
}

// 清理
new File(sourceFile).delete();
new File(targetFile).delete();
}

文件字符输入输出流

FileReaderInputStreamReader 的便捷子类专门用于读取文本文件它使用平台默认字符集简化了文件读取操作

  • 直接读取文本文件
  • 使用平台默认字符集注意可能导致编码问题
  • 适合读取系统默认编码的文本文件

FileWriterOutputStreamWriter 的便捷子类专门用于向文本文件写入字符数据它使用平台默认字符集简化了文件写入操作

  • 直接写入文本文件
  • 使用平台默认字符集注意可能导致编码问题
  • 支持覆盖和追加两种模式

⭐⭐ 写入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void basicWrite() {
// 覆盖模式写入
try (FileWriter fw = new FileWriter("basic.txt")) {
fw.write("Hello, FileWriter!\n");
fw.write("这是第一行\n");
fw.write("这是第二行\n");
fw.write("这是第三行");

System.out.println("文件写入完成: basic.txt");

} catch (IOException e) {
e.printStackTrace();
}

// 读取并显示内容
displayFileContent("basic.txt");
}

⭐⭐ 追加模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void appendMode() {
// 第一次写入
try (FileWriter fw = new FileWriter("append.txt")) {
fw.write("初始内容\n");
System.out.println("第一次写入完成");
} catch (IOException e) {
e.printStackTrace();
}

// 追加模式第二个参数true
try (FileWriter fw = new FileWriter("append.txt", true)) {
fw.write("追加的内容\n");
fw.write("第二行追加内容\n");
System.out.println("追加写入完成");
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 批量写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void batchWrite() {
String[] lines = {
"批量写入第1行",
"批量写入第2行",
"批量写入第3行",
"批量写入第4行",
"批量写入第5行"
};

try (FileWriter fw = new FileWriter("batch.txt")) {
for (String line : lines) {
fw.write(line + "\n");
}
System.out.println("批量写入 " + lines.length + " 行数据");
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 写入不同类型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void writeDifferentTypes() {
try (FileWriter fw = new FileWriter("types.txt")) {
// 写入字符串
fw.write("字符串: Hello\n");

// 写入字符
fw.write("字符: ");
fw.write('A');
fw.write('\n');

// 写入整数转换为字符串
int number = 12345;
fw.write("整数: " + number + "\n");

// 写入浮点数
double pi = 3.14159;
fw.write("浮点数: " + pi + "\n");

// 写入布尔值
boolean flag = true;
fw.write("布尔值: " + flag + "\n");

// 写入字符数组
char[] chars = {'数', '组', '测', '试'};
fw.write("字符数组: ");
fw.write(chars);
fw.write('\n');

System.out.println("多种类型数据写入完成");
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void basicFileRead() {
// 使用 FileReader 读取
try (FileReader fr = new FileReader("test.txt")) {
int character;
System.out.print("读取内容: ");
while ((character = fr.read()) != -1) {
System.out.print((char) character);
}
System.out.println();
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 批量读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void batchRead() {
try (FileReader fr = new FileReader("test.txt")) {
char[] buffer = new char[1024];
int bytesRead;

while ((bytesRead = fr.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, bytesRead));
}
System.out.println();

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 逐字符读取并统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static void characterByCharacter() {
try (FileReader fr = new FileReader("test.txt")) {
int ch;
int letterCount = 0;
int digitCount = 0;
int chineseCount = 0;
int spaceCount = 0;
int otherCount = 0;

while ((ch = fr.read()) != -1) {
char c = (char) ch;
if (Character.isLetter(c)) {
// 判断是否是中文字符
if (c >= 0x4E00 && c <= 0x9FFF) {
chineseCount++;
} else {
letterCount++;
}
} else if (Character.isDigit(c)) {
digitCount++;
} else if (Character.isSpaceChar(c)) {
spaceCount++;
} else {
otherCount++;
}
}

System.out.println("英文字母: " + letterCount);
System.out.println("中文字符: " + chineseCount);
System.out.println("数字: " + digitCount);
System.out.println("空格: " + spaceCount);
System.out.println("其他字符: " + otherCount);

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 指定编码读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void recommendedApproach() {
String content = "这是使用OutputStreamWriter明确指定UTF-8编码写入的内容";

// 推荐使用OutputStreamWriter明确指定编码
try (FileOutputStream fos = new FileOutputStream("recommended.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8")) {

osw.write(content);
System.out.println("使用OutputStreamWriter指定UTF-8编码写入完成");

} catch (IOException e) {
e.printStackTrace();
}

// 读取数据
try (FileInputStream fis = new FileInputStream("recommended.txt");
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
BufferedReader br = new BufferedReader(isr)) {

String line = br.readLine();
System.out.println("读取验证: " + line);
System.out.println("内容匹配: " + content.equals(line));
} catch (IOException e) {
e.printStackTrace();
}

new File("recommended.txt").delete();
}

缓冲字符输入输出流

BufferedReader 为字符输入流提供缓冲功能并提供了 readLine() 方法可以高效地按行读取文本

  • 提供缓冲功能减少I/O操作次数
  • 提供 readLine() 方法方便按行读取
  • 支持标记和重置
  • 可以包装任何Reader

BufferedWriter 为字符输出流提供缓冲功能并提供 newLine() 方法用于写入平台独立的换行符

  • 提供缓冲功能减少I/O操作次数
  • 提供 newLine() 方法写入平台独立的换行符
  • 支持自定义缓冲区大小

⭐⭐ 写入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void basicUsage() {
try (BufferedWriter bw = new BufferedWriter(new FileWriter("basic_bw.txt"))) {

bw.write("第一行内容");
bw.newLine(); // 写入平台独立的换行符
bw.write("第二行内容");
bw.newLine();
bw.write("第三行内容");

System.out.println("写入完成");

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 缓冲区大小设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void bufferSizeDemo() {
// 默认缓冲区大小通常8192字节
try (BufferedWriter bw = new BufferedWriter(new FileWriter("default_buffer.txt"))) {
bw.write("使用默认缓冲区大小");
System.out.println("默认缓冲区: 8192字节");
} catch (IOException e) {
e.printStackTrace();
}

// 自定义缓冲区大小32KB
try (BufferedWriter bw = new BufferedWriter(new FileWriter("custom_buffer.txt"), 32768)) {
bw.write("使用32KB自定义缓冲区");
System.out.println("自定义缓冲区: 32768字节");
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 写入 CSV 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void writeCSVFile() {
String[][] data = {
{"姓名", "年龄", "城市", "职业"},
{"张三", "25", "北京", "工程师"},
{"李四", "30", "上海", "设计师"},
{"王五", "28", "广州", "产品经理"},
{"赵六", "35", "深圳", "架构师"}
};

try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.csv"))) {

for (String[] row : data) {
// 使用String.join将数组连接成CSV行
String line = String.join(",", row);
bw.write(line);
bw.newLine();
}

System.out.println("CSV文件写入完成共 " + data.length + " 行");

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 写入 HTML 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public static void writeHTMLFile() {
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.html"))) {

bw.write("<!DOCTYPE html>");
bw.newLine();
bw.write("<html>");
bw.newLine();
bw.write("<head>");
bw.newLine();
bw.write(" <title>BufferedWriter示例</title>");
bw.newLine();
bw.write(" <style>");
bw.newLine();
bw.write(" body { font-family: Arial; margin: 40px; }");
bw.newLine();
bw.write(" h1 { color: #333; }");
bw.newLine();
bw.write(" </style>");
bw.newLine();
bw.write("</head>");
bw.newLine();
bw.write("<body>");
bw.newLine();
bw.write(" <h1>BufferedWriter生成的HTML</h1>");
bw.newLine();
bw.write(" <p>这是使用BufferedWriter动态生成的HTML文件</p>");
bw.newLine();
bw.write(" <ul>");
bw.newLine();
bw.write(" <li>支持newLine()方法</li>");
bw.newLine();
bw.write(" <li>自动处理换行符</li>");
bw.newLine();
bw.write(" <li>提高写入性能</li>");
bw.newLine();
bw.write(" </ul>");
bw.newLine();
bw.write("</body>");
bw.newLine();
bw.write("</html>");

System.out.println("HTML文件写入完成: output.html");

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 写入配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void writeCSVFile() {
try (BufferedWriter bw = new BufferedWriter(new FileWriter("config.properties"))) {
bw.write("# 数据库配置\n");
bw.write("db.url=jdbc:mysql://localhost:3306/test\n");
bw.write("db.username=root\n");
bw.write("db.password=123456\n");
bw.write("\n");
bw.write("# 应用配置\n");
bw.write("app.name=MyApplication\n");
bw.write("app.version=1.0.0\n");
bw.write("app.debug=true\n");
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 按行读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void basicReadLine() {
try (FileReader fr = new FileReader("lines.txt");
BufferedReader br = new BufferedReader(fr)) {

String line;
int lineNum = 1;

// readLine() 方法返回一行文本不包括换行符
// 到达文件末尾返回 null
while ((line = br.readLine()) != null) {
System.out.println(lineNum + ": " + line);
lineNum++;
}

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 读取 CSV 文件并解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void readCSVFile() {
List<Map<String, String>> records = new ArrayList<>();

try (FileReader fr = new FileReader("data.csv");
BufferedReader br = new BufferedReader(fr)) {

// 读取表头
String headerLine = br.readLine();
if (headerLine == null) return;

String[] headers = headerLine.split(",");

// 读取数据行
String line;
while ((line = br.readLine()) != null) {
String[] values = line.split(",");
Map<String, String> record = new LinkedHashMap<>();

for (int i = 0; i < headers.length && i < values.length; i++) {
record.put(headers[i], values[i]);
}
records.add(record);
}

// 显示解析结果
for (Map<String, String> record : records) {
System.out.println(record);
}

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 读取配置文件类似.properties格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void readPropertiesFile() {
Properties props = new Properties();

try (FileReader fr = new FileReader("config.properties");
BufferedReader br = new BufferedReader(fr)) {

String line;
while ((line = br.readLine()) != null) {
// 跳过空行和注释行
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) {
continue;
}

// 解析键值对
int separatorIndex = line.indexOf('=');
if (separatorIndex > 0) {
String key = line.substring(0, separatorIndex).trim();
String value = line.substring(separatorIndex + 1).trim();
props.setProperty(key, value);
}
}

// 显示配置
System.out.println("数据库URL: " + props.getProperty("db.url"));
System.out.println("用户名: " + props.getProperty("db.username"));
System.out.println("应用名称: " + props.getProperty("app.name"));
System.out.println("调试模式: " + props.getProperty("app.debug"));

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 标记和重置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static void markAndReset() {
String content = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5";
try (StringReader sr = new StringReader(content);
BufferedReader br = new BufferedReader(sr)) {

// 检查是否支持标记
if (br.markSupported()) {
System.out.println("支持 mark/reset 操作");

// 读取第一行
String line1 = br.readLine();
System.out.println("第一行: " + line1);

// 标记当前位置参数指定可读的最大字符数
br.mark(1000);

// 读取第二行和第三行
String line2 = br.readLine();
String line3 = br.readLine();
System.out.println("第二行: " + line2);
System.out.println("第三行: " + line3);

// 重置到标记位置
br.reset();
System.out.println("\n重置后重新读取:");

// 重新读取第二行和第三行
System.out.println("第二行: " + br.readLine());
System.out.println("第三行: " + br.readLine());
}

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 读取多行用户输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void readMultiLineInput() {
// 从控制台读取
try (InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr)) {

List<String> lines = new ArrayList<>();
String line;

while ((line = br.readLine()) != null && !line.isEmpty()) {
lines.add(line);
}

System.out.println("\n您输入了 " + lines.size() + " 行:");
for (String l : lines) {
System.out.println(" " + l);
}

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 处理大日志文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public static void processLogFile() {
long startTime = System.currentTimeMillis();
int totalLines = 0;
int errorCount = 0;
int warningCount = 0;
int infoCount = 0;

try (FileReader fr = new FileReader("app.log");
BufferedReader br = new BufferedReader(fr)) {

String line;
while ((line = br.readLine()) != null) {
totalLines++;

// 统计日志级别
if (line.contains("[ERROR]")) {
errorCount++;
} else if (line.contains("[WARN]")) {
warningCount++;
} else if (line.contains("[INFO]")) {
infoCount++;
}

// 每1000行显示进度
if (totalLines % 1000 == 0) {
System.out.println("已处理 " + totalLines + " 行...");
}
}

long endTime = System.currentTimeMillis();

System.out.println("\n=== 统计结果 ===");
System.out.println("总行数: " + totalLines);
System.out.println("ERROR: " + errorCount);
System.out.println("WARN: " + warningCount);
System.out.println("INFO: " + infoCount);
System.out.println("处理时间: " + (endTime - startTime) + "ms");

} catch (IOException e) {
e.printStackTrace();
}

// 清理
new File("app.log").delete();
}

行号字符输入流

LineNumberReaderBufferedReader 的子类它在读取文本时自动跟踪行号并提供获取和设置行号的方法

  • 自动维护行号计数器
  • 继承 BufferedReader 的所有功能
  • 提供 getLineNumber() 和 setLineNumber() 方法
  • 适合需要行号信息的场景如编译器代码分析工具

⭐⭐ 显示行号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void basicLineNumber() {
try (FileReader fr = new FileReader("sample.txt");
LineNumberReader lnr = new LineNumberReader(fr)) {

String line;
// 行号从0开始第一次调用 getLineNumber() 返回0
// 读取第一行后行号变为1
while ((line = lnr.readLine()) != null) {
// getLineNumber() 返回当前行号
System.out.println(lnr.getLineNumber() + ": " + line);
}

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 统计代码行数忽略空行和注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public static void codeAnalyzer() {
int totalLines = 0;
int codeLines = 0;
int commentLines = 0;
int blankLines = 0;

try (FileReader fr = new FileReader("SampleCode.java");
LineNumberReader lnr = new LineNumberReader(fr)) {

String line;
boolean inBlockComment = false;

while ((line = lnr.readLine()) != null) {
totalLines++;
String trimmed = line.trim();

// 统计空行
if (trimmed.isEmpty()) {
blankLines++;
continue;
}

// 处理块注释
if (inBlockComment) {
commentLines++;
if (trimmed.contains("*/")) {
inBlockComment = false;
}
continue;
}

// 检查块注释开始
if (trimmed.startsWith("/*")) {
commentLines++;
if (!trimmed.contains("*/")) {
inBlockComment = true;
}
continue;
}

// 检查行注释
if (trimmed.startsWith("//")) {
commentLines++;
continue;
}

// 代码行
codeLines++;
}

System.out.println("=== 统计结果 ===");
System.out.println("总行数: " + totalLines);
System.out.println("代码行: " + codeLines);
System.out.println("注释行: " + commentLines);
System.out.println("空行: " + blankLines);
System.out.println("代码率: " + (codeLines * 100.0 / totalLines) + "%");

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 错误定位在文件中查找并定位错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public static void errorLocator() {
try (FileReader fr = new FileReader("config_with_error.properties");
LineNumberReader lnr = new LineNumberReader(fr)) {

String line;
List<String> errors = new java.util.ArrayList<>();

while ((line = lnr.readLine()) != null) {
int lineNum = lnr.getLineNumber();
String trimmed = line.trim();

// 跳过空行和注释
if (trimmed.isEmpty() || trimmed.startsWith("#")) {
continue;
}

// 检查格式错误
if (!trimmed.contains("=")) {
errors.add("行 " + lineNum + ": 缺少等号分隔符 - " + trimmed);
continue;
}

String[] parts = trimmed.split("=", 2);
String key = parts[0].trim();
String value = parts[1].trim();

// 检查键名是否为空
if (key.isEmpty()) {
errors.add("行 " + lineNum + ": 键名为空");
}

// 检查值是否为空
if (value.isEmpty()) {
errors.add("行 " + lineNum + ": 值为空");
}

// 检查端口号格式
if (key.equals("port")) {
try {
int port = Integer.parseInt(value);
if (port < 1 || port > 65535) {
errors.add("行 " + lineNum + ": 端口号超出范围 (1-65535) - " + port);
}
} catch (NumberFormatException e) {
errors.add("行 " + lineNum + ": 端口号格式错误 - " + value);
}
}
}

if (errors.isEmpty()) {
System.out.println("配置文件格式正确");
} else {
System.out.println("发现 " + errors.size() + " 个错误:");
for (String error : errors) {
System.out.println(" " + error);
}
}

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 语法高亮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public static void syntaxHighlighter() {
String[] keywords = {"public", "class", "static", "void", "int",
"String", "if", "else", "for", "while", "return"};

try (FileReader fr = new FileReader("SampleCode.java");
LineNumberReader lnr = new LineNumberReader(fr)) {

String line;
while ((line = lnr.readLine()) != null) {
int lineNum = lnr.getLineNumber();
String highlighted = line;

// 模拟高亮关键字
for (String keyword : keywords) {
highlighted = highlighted.replaceAll(
"\\b" + keyword + "\\b",
"\033[34m" + keyword + "\033[0m" // 蓝色
);
}

// 高亮字符串
highlighted = highlighted.replaceAll(
"\"([^\"]*)\"",
"\033[32m\"$1\"\033[0m" // 绿色
);

// 高亮注释
highlighted = highlighted.replaceAll(
"//.*$",
"\033[90m$0\033[0m" // 灰色
);

System.out.printf("%4d | %s%n", lineNum, highlighted);
}

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 搜索并显示行号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void searchWithLineNumbers() {
String searchTerm = "error";

try (FileReader fr = new FileReader("search.log");
LineNumberReader lnr = new LineNumberReader(fr)) {

String line;
int matchCount = 0;

System.out.println("匹配结果:");
while ((line = lnr.readLine()) != null) {
if (line.toLowerCase().contains(searchTerm.toLowerCase())) {
matchCount++;
System.out.printf("行 %4d: %s%n", lnr.getLineNumber(),
line.substring(0, Math.min(80, line.length())));
}
}

System.out.println("\n共找到 " + matchCount + " 处匹配");
} catch (IOException e) {
e.printStackTrace();
}
}

字符数组输入输出流

CharArrayReader 从字符数组中读取数据将内存中的字符数组包装成字符输入流

  • 数据源是内存中的字符数组
  • 不需要关闭流close()无效
  • 支持标记和重置
  • 适合处理临时字符数据

CharArrayWriter 将字符数据写入内存中的字符数组缓冲区它会自动管理缓冲区大小可以根据需要增长

  • 数据写入内存不涉及磁盘
  • 自动扩容的缓冲区
  • 可以获取字符数组或字符串
  • 不需要关闭流
  • 可以写入到其他Writer

⭐⭐ 写入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void basicUsage() {
try (CharArrayWriter caw = new CharArrayWriter()) {
// 写入各种数据
caw.write('H');
caw.write('e');
caw.write('l');
caw.write('l');
caw.write('o');
caw.write(", ");
caw.write("World!");
caw.write(" 你好世界");

// 获取结果
char[] chars = caw.toCharArray();
String str = caw.toString();

System.out.println("字符数组长度: " + chars.length);
System.out.println("字符串内容: " + str);
System.out.println("当前大小: " + caw.size() + " 字符");

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 写入到其他流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void writeToOtherWriter() {
try (CharArrayWriter caw = new CharArrayWriter();
FileWriter fw = new FileWriter("caw_output.txt");
StringWriter sw = new StringWriter()) {
// 准备数据
caw.write("这是要写入文件的内容\n");
caw.write("这是要写入StringWriter的内容\n");
caw.write("可以同时写入多个目标\n");

// 写入到文件
caw.writeTo(fw);
System.out.println("已写入文件: caw_output.txt");

// 写入到StringWriter
caw.writeTo(sw);
System.out.println("StringWriter内容: " + sw.toString());
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 重置和重用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void resetAndReuse() {
try (CharArrayWriter caw = new CharArrayWriter()) {
// 第一批数据
caw.write("第一批数据");
System.out.println("第一批数据: " + caw.toString());
System.out.println("大小: " + caw.size());

// 重置缓冲区
caw.reset();
System.out.println("\n重置后大小: " + caw.size());

// 第二批数据
caw.write("第二批数据");
System.out.println("第二批数据: " + caw.toString());
System.out.println("大小: " + caw.size());

// 再次重置
caw.reset();
caw.write("第三批数据");
System.out.println("\n第三批数据: " + caw.toString());
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 构建字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void buildString() {
try (CharArrayWriter caw = new CharArrayWriter()) {
// 构建JSON字符串
caw.write("{");
caw.write("\"name\": \"张三\",");
caw.write("\"age\": 25,");
caw.write("\"city\": \"北京\",");
caw.write("\"skills\": [\"Java\", \"Python\", \"JavaScript\"]");
caw.write("}");

String json = caw.toString();
System.out.println("生成的JSON:");
System.out.println(json);

// 构建XML
caw.reset();
caw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
caw.write("<user>\n");
caw.write(" <name>李四</name>\n");
caw.write(" <age>30</age>\n");
caw.write(" <city>上海</city>\n");
caw.write("</user>");

String xml = caw.toString();
System.out.println("\n生成的XML:");
System.out.println(xml);
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void basicUsage() {
char[] data = "Hello, CharArrayReader! 这是测试数据".toCharArray();
try (CharArrayReader car = new CharArrayReader(data)) {
int ch;
System.out.print("读取内容: ");
while ((ch = car.read()) != -1) {
System.out.print((char) ch);
}
System.out.println();
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 标记和重置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void markAndReset() {
char[] data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
try (CharArrayReader car = new CharArrayReader(data)) {
// 读取前5个字符
char[] buffer = new char[5];
car.read(buffer);
System.out.println("前5个字符: " + new String(buffer));

// 标记当前位置
car.mark(100);

// 继续读取5个字符
car.read(buffer);
System.out.println("接下来5个字符: " + new String(buffer));

// 重置到标记位置
car.reset();
car.read(buffer);
System.out.println("重置后读取: " + new String(buffer));
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 部分读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void partialRead() {
char[] data = "Java Programming Language".toCharArray();
try (CharArrayReader car = new CharArrayReader(data, 5, 11)) {
// 只读取从索引5开始的11个字符
int ch;
System.out.print("部分读取: ");
while ((ch = car.read()) != -1) {
System.out.print((char) ch);
}
System.out.println();
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 字符串解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static void stringParser() {
String expression = "10 + 20 * 30 - 40 / 5";
try (CharArrayReader car = new CharArrayReader(expression.toCharArray())) {
StringBuilder token = new StringBuilder();
int ch;

System.out.println("=== 解析表达式 ===");
System.out.print("Tokens: ");

while ((ch = car.read()) != -1) {
char c = (char) ch;

if (Character.isWhitespace(c)) {
if (token.length() > 0) {
System.out.print(token + " ");
token.setLength(0);
}
} else if (c == '+' || c == '-' || c == '*' || c == '/') {
if (token.length() > 0) {
System.out.print(token + " ");
token.setLength(0);
}
System.out.print(c + " ");
} else {
token.append(c);
}
}

if (token.length() > 0) {
System.out.print(token);
}
System.out.println();

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 字符数组操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void charArrayOperations() {
// 准备数据
String[] lines = {
"Line 1: First line",
"Line 2: Second line",
"Line 3: Third line"
};

// 将所有行合并到一个字符数组
StringBuilder sb = new StringBuilder();
for (String line : lines) {
sb.append(line).append('\n');
}
char[] data = sb.toString().toCharArray();

// 使用 CharArrayReader 逐行处理
try (CharArrayReader car = new CharArrayReader(data);
BufferedReader br = new BufferedReader(car)) {
System.out.println("\n=== 逐行处理 ===");
String line;
int lineNum = 1;

while ((line = br.readLine()) != null) {
System.out.println(lineNum + ": " + line);
lineNum++;
}
} catch (IOException e) {
e.printStackTrace();
}
}

字符串输入输出流

StringReader 从字符串中读取字符数据将字符串包装成字符输入流

  • 数据源是字符串
  • 简单高效适合处理字符串数据
  • 支持标记和重置
  • 不需要关闭流

StringWriter 将字符数据写入内存中的字符串缓冲区它内部维护一个 StringBuilder可以方便地获取最终字符串

  • 数据写入内存字符串
  • 内部使用StringBuilder
  • 不需要关闭流
  • 适合构建字符串
  • 线程安全方法同步

⭐⭐ 写入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void basicUsage() {
try (StringWriter sw = new StringWriter()) {
// 写入各种数据
sw.write("Hello");
sw.write(", ");
sw.write("StringWriter");
sw.write("!");
sw.append(' ');
sw.append("This is appended.");

// 获取结果
String result = sw.toString();
StringBuffer buffer = sw.getBuffer(); // 获取内部StringBuffer

System.out.println("结果: " + result);
System.out.println("StringBuffer内容: " + buffer.toString());
System.out.println("长度: " + sw.getBuffer().length());
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 读取数据

1
2
3
4
5
6
7
8
9
10
11
public static void basicUsage() {
String text = "Hello, StringReader! 这是字符串读取测试";
try (StringReader sr = new StringReader(text)) {
char[] buffer = new char[1024];
int length = sr.read(buffer);
System.out.println("读取内容: " + new String(buffer, 0, length));

} catch (IOException e) {
e.printStackTrace();
}
}

管道字符输入输出流

PipedReaderPipedWriter 配合使用实现线程间的字符数据通信

  • 线程间通信
  • 字符数据管道
  • 必须成对使用
  • 线程安全

⭐⭐ 基本通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public static void basicCommunication() {
try {
PipedReader reader = new PipedReader();
PipedWriter writer = new PipedWriter();

// 连接管道
reader.connect(writer);

// 创建发送线程
Thread sender = new Thread(() -> {
try {
String[] messages = {"Hello", "World", "Piped", "Reader", "Writer"};
for (String msg : messages) {
writer.write(msg);
writer.write('\n');
writer.flush();
Thread.sleep(500);
}
writer.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});

// 创建接收线程
Thread receiver = new Thread(() -> {
try (BufferedReader br = new BufferedReader(reader)) {
String line;
System.out.println("接收线程开始读取...");
while ((line = br.readLine()) != null) {
System.out.println("收到: " + line);
}
System.out.println("通信结束");
} catch (IOException e) {
e.printStackTrace();
}
});

receiver.start();
Thread.sleep(100);
sender.start();

sender.join();
receiver.join();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}

⭐⭐ 生产者-消费者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public static void producerConsumer() {
try {
PipedReader reader = new PipedReader(1024); // 设置管道大小
PipedWriter writer = new PipedWriter();
reader.connect(writer);

// 生产者生成数据
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 20; i++) {
String data = "数据-" + i;
writer.write(data);
writer.write('\n');
writer.flush();
System.out.println("生产: " + data);
Thread.sleep(200);
}
writer.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});

// 消费者处理数据
Thread consumer = new Thread(() -> {
try (BufferedReader br = new BufferedReader(reader)) {
String line;
int count = 0;
while ((line = br.readLine()) != null) {
System.out.println("消费: " + line + " -> 处理中...");
Thread.sleep(300); // 模拟处理时间
count++;
}
System.out.println("共处理 " + count + " 条数据");
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});

consumer.start();
Thread.sleep(100);
producer.start();

producer.join();
consumer.join();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}

⭐⭐ 多管道通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public static void multiChannelCommunication() {
try {
// 创建多个管道对
PipedReader readerA = new PipedReader();
PipedWriter writerA = new PipedWriter();
PipedReader readerB = new PipedReader();
PipedWriter writerB = new PipedWriter();

readerA.connect(writerA);
readerB.connect(writerB);

// 双向通信
Thread thread1 = new Thread(() -> {
try (BufferedReader br = new BufferedReader(readerA);
PrintWriter pw = new PrintWriter(writerB)) {

String[] messages = {"你好", "今天天气不错", "吃了吗", "再见"};
for (String msg : messages) {
System.out.println("线程1发送: " + msg);
pw.println(msg);
pw.flush();

// 读取响应
String response = br.readLine();
System.out.println("线程1收到: " + response);
Thread.sleep(1000);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});

Thread thread2 = new Thread(() -> {
try (BufferedReader br = new BufferedReader(readerB);
PrintWriter pw = new PrintWriter(writerA)) {

String line;
while ((line = br.readLine()) != null) {
System.out.println("线程2收到: " + line);
String response = "响应: " + line;
System.out.println("线程2发送: " + response);
pw.println(response);
pw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
});

thread2.start();
Thread.sleep(100);
thread1.start();

thread1.join();
thread2.interrupt();

} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}

⭐⭐ 带超时的通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public static void timeoutCommunication() {
try {
PipedReader reader = new PipedReader();
PipedWriter writer = new PipedWriter();
reader.connect(writer);

ExecutorService executor = Executors.newSingleThreadExecutor();

// 发送线程
Thread sender = new Thread(() -> {
try {
String[] messages = {"消息1", "消息2", "消息3"};
for (String msg : messages) {
writer.write(msg);
writer.write('\n');
writer.flush();
System.out.println("发送: " + msg);
Thread.sleep(2000); // 模拟慢速发送
}
writer.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});

// 带超时的接收
Future<?> future = executor.submit(() -> {
try (BufferedReader br = new BufferedReader(reader)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println("接收: " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});

sender.start();

try {
// 等待最多3秒
future.get(3, TimeUnit.SECONDS);
} catch (TimeoutException e) {
System.out.println("接收超时取消操作");
future.cancel(true);
} catch (Exception e) {
e.printStackTrace();
}

executor.shutdown();
sender.interrupt();
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 数据处理流水线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public static void processingPipeline() {
try {
// 创建多个管道形成流水线
PipedReader reader1 = new PipedReader();
PipedWriter writer1 = new PipedWriter();
PipedReader reader2 = new PipedReader();
PipedWriter writer2 = new PipedWriter();

reader1.connect(writer1);
reader2.connect(writer2);

// 阶段1数据生成
Thread stage1 = new Thread(() -> {
try {
String[] rawData = {"apple", "BANANA", "cherry", "DATE", "elderberry"};
for (String data : rawData) {
writer1.write(data);
writer1.write('\n');
writer1.flush();
System.out.println("阶段1-生成: " + data);
Thread.sleep(100);
}
writer1.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});

// 阶段2数据转换转小写
Thread stage2 = new Thread(() -> {
try (BufferedReader br = new BufferedReader(reader1);
PrintWriter pw = new PrintWriter(writer2)) {

String line;
while ((line = br.readLine()) != null) {
String transformed = line.toLowerCase();
pw.println(transformed);
pw.flush();
System.out.println("阶段2-转换: " + line + " -> " + transformed);
Thread.sleep(100);
}
writer2.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});

// 阶段3数据输出
Thread stage3 = new Thread(() -> {
try (BufferedReader br = new BufferedReader(reader2)) {
String line;
int count = 0;
while ((line = br.readLine()) != null) {
System.out.println("阶段3-输出: " + line);
count++;
}
System.out.println("总计处理: " + count + " 条数据");
} catch (IOException e) {
e.printStackTrace();
}
});

stage3.start();
stage2.start();
stage1.start();

stage1.join();
stage2.join();
stage3.join();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}

过滤字符输入输出流

FilterReader 是所有过滤字符输入流的抽象基类它包装了另一个 Reader可以在读取数据时添加额外的功能

FilterWriter 是所有过滤字符输出流的抽象基类它包装了另一个Writer可以在写入数据时添加额外的功能

  • 抽象类不能直接实例化
  • 子类实现具体功能

⭐⭐ 输入流过滤转换为大写的过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
static class UppercaseFilterReader extends FilterReader {
protected UppercaseFilterReader(Reader in) {
super(in);
}

@Override
public int read() throws IOException {
int ch = super.read();
if (ch != -1) {
return Character.toUpperCase(ch);
}
return ch;
}

@Override
public int read(char[] cbuf, int off, int len) throws IOException {
int count = super.read(cbuf, off, len);
if (count > 0) {
for (int i = off; i < off + count; i++) {
cbuf[i] = Character.toUpperCase(cbuf[i]);
}
}
return count;
}
}

public static void uppercaseFilter() {
String text = "Hello, World! 你好世界";

try (StringReader sr = new StringReader(text);
UppercaseFilterReader ufr = new UppercaseFilterReader(sr)) {

int ch;
System.out.print("大写转换: ");
while ((ch = ufr.read()) != -1) {
System.out.print((char) ch);
}
System.out.println();

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 输入流过滤移除多余空白的过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
static class RemoveWhitespaceFilterReader extends FilterReader {
private boolean lastWasSpace = false;

protected RemoveWhitespaceFilterReader(Reader in) {
super(in);
}

@Override
public int read() throws IOException {
int ch = super.read();
if (ch == -1) return -1;

// 如果是空格检查是否需要保留
if (Character.isWhitespace(ch)) {
if (lastWasSpace) {
// 跳过多余空格
return read();
}
lastWasSpace = true;
return ' ';
}
lastWasSpace = false;
return ch;
}
}

public static void removeWhitespaceFilter() {
String text = "Hello World! This is a test.";

try (StringReader sr = new StringReader(text);
RemoveWhitespaceFilterReader rwf = new RemoveWhitespaceFilterReader(sr)) {

int ch;
System.out.print("移除多余空格: ");
while ((ch = rwf.read()) != -1) {
System.out.print((char) ch);
}
System.out.println();

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 输入流过滤XOR加密/解密过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
static class XorFilterReader extends FilterReader {
private final byte key;

protected XorFilterReader(Reader in, byte key) {
super(in);
this.key = key;
}

@Override
public int read() throws IOException {
int ch = super.read();
if (ch != -1) {
// 对字符进行XOR加密保留字符范围
return ch ^ key;
}
return ch;
}

@Override
public int read(char[] cbuf, int off, int len) throws IOException {
int count = super.read(cbuf, off, len);
if (count > 0) {
for (int i = off; i < off + count; i++) {
cbuf[i] = (char) (cbuf[i] ^ key);
}
}
return count;
}
}

public static void encryptionFilter() {
String text = "这是需要加密的文本";
byte key = 0x55;

// 加密
try (StringReader sr = new StringReader(text);
XorFilterReader xorReader = new XorFilterReader(sr, key);
StringWriter sw = new StringWriter()) {

int ch;
while ((ch = xorReader.read()) != -1) {
sw.write(ch);
}

String encrypted = sw.toString();
System.out.println("原始文本: " + text);
System.out.println("加密文本: " + encrypted);

// 解密
try (StringReader sr2 = new StringReader(encrypted);
XorFilterReader xorReader2 = new XorFilterReader(sr2, key);
StringWriter sw2 = new StringWriter()) {

while ((ch = xorReader2.read()) != -1) {
sw2.write(ch);
}
System.out.println("解密文本: " + sw2.toString());
}

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 输入流过滤链式过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void chainedFilters() {
String text = " Hello, World! This is a test. ";

try (StringReader sr = new StringReader(text);
RemoveWhitespaceFilterReader rwf = new RemoveWhitespaceFilterReader(sr);
UppercaseFilterReader ufr = new UppercaseFilterReader(rwf)) {

int ch;
System.out.print("\n链式过滤移除空格+大写: ");
while ((ch = ufr.read()) != -1) {
System.out.print((char) ch);
}
System.out.println();
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 输出流过滤大写过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
static class UppercaseFilterWriter extends FilterWriter {
public UppercaseFilterWriter(Writer out) {
super(out);
}

@Override
public void write(int c) throws IOException {
out.write(Character.toUpperCase(c));
}

@Override
public void write(char[] cbuf, int off, int len) throws IOException {
char[] upper = new char[len];
for (int i = 0; i < len; i++) {
upper[i] = Character.toUpperCase(cbuf[off + i]);
}
out.write(upper, 0, len);
}

@Override
public void write(String str, int off, int len) throws IOException {
String upper = str.substring(off, off + len).toUpperCase();
out.write(upper);
}
}

public static void uppercaseFilter() {
try (StringWriter sw = new StringWriter();
UppercaseFilterWriter ufw = new UppercaseFilterWriter(sw)) {
ufw.write("Hello, World!");
ufw.write(" 你好世界");

System.out.println("原始输出应该包含小写");
System.out.println("实际输出: " + sw.toString());
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 输出流过滤行号过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
static class LineNumberFilterWriter extends FilterWriter {
private int lineNumber = 1;
private boolean atStartOfLine = true;

public LineNumberFilterWriter(Writer out) {
super(out);
}

@Override
public void write(int c) throws IOException {
if (atStartOfLine) {
String lineNum = String.format("%4d: ", lineNumber++);
out.write(lineNum);
atStartOfLine = false;
}

out.write(c);

if (c == '\n') {
atStartOfLine = true;
}
}

@Override
public void write(char[] cbuf, int off, int len) throws IOException {
for (int i = 0; i < len; i++) {
write(cbuf[off + i]);
}
}

@Override
public void write(String str, int off, int len) throws IOException {
for (int i = 0; i < len; i++) {
write(str.charAt(off + i));
}
}
}

public static void lineNumberFilter() {
System.out.println("\n=== 行号过滤器 ===");

try (StringWriter sw = new StringWriter();
LineNumberFilterWriter lnfw = new LineNumberFilterWriter(sw)) {

lnfw.write("第一行内容\n");
lnfw.write("第二行内容\n");
lnfw.write("第三行内容\n");
lnfw.write("第四行内容");

System.out.println("带行号的输出:");
System.out.println(sw.toString());

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 输出流过滤HTML转义过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
static class HtmlEscapeFilterWriter extends FilterWriter {
public HtmlEscapeFilterWriter(Writer out) {
super(out);
}

@Override
public void write(int c) throws IOException {
char ch = (char) c;
switch (ch) {
case '<':
out.write("&lt;");
break;
case '>':
out.write("&gt;");
break;
case '&':
out.write("&amp;");
break;
case '"':
out.write("&quot;");
break;
case '\'':
out.write("&#39;");
break;
default:
out.write(c);
}
}

@Override
public void write(char[] cbuf, int off, int len) throws IOException {
for (int i = 0; i < len; i++) {
write(cbuf[off + i]);
}
}

@Override
public void write(String str, int off, int len) throws IOException {
for (int i = 0; i < len; i++) {
write(str.charAt(off + i));
}
}
}

public static void htmlEscapeFilter() {
System.out.println("\n=== HTML转义过滤器 ===");

try (StringWriter sw = new StringWriter();
HtmlEscapeFilterWriter hefw = new HtmlEscapeFilterWriter(sw)) {

hefw.write("<html>");
hefw.write(" <body>");
hefw.write(" <h1>Hello & Welcome</h1>");
hefw.write(" <p>This is a test.</p>");
hefw.write(" </body>");
hefw.write("</html>");

System.out.println("转义后的HTML:");
System.out.println(sw.toString());

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 输出流过滤压缩空格过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
static class CompressWhitespaceFilterWriter extends FilterWriter {
private boolean lastWasSpace = false;

public CompressWhitespaceFilterWriter(Writer out) {
super(out);
}

@Override
public void write(int c) throws IOException {
char ch = (char) c;

if (Character.isWhitespace(ch)) {
if (!lastWasSpace) {
out.write(' ');
lastWasSpace = true;
}
} else {
out.write(c);
lastWasSpace = false;
}
}

@Override
public void write(char[] cbuf, int off, int len) throws IOException {
for (int i = 0; i < len; i++) {
write(cbuf[off + i]);
}
}

@Override
public void write(String str, int off, int len) throws IOException {
for (int i = 0; i < len; i++) {
write(str.charAt(off + i));
}
}
}

public static void compressWhitespaceFilter() {
System.out.println("\n=== 压缩空格过滤器 ===");

try (StringWriter sw = new StringWriter();
CompressWhitespaceFilterWriter cwf = new CompressWhitespaceFilterWriter(sw)) {

cwf.write("This is a test with multiple spaces.");
cwf.write("\n\n");
cwf.write(" Multiple lines with spaces . ");

System.out.println("压缩空格后的输出:");
System.out.println(sw.toString());

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 输出流过滤链式过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void chainedFilters() {
System.out.println("\n=== 链式过滤器 ===");

try (StringWriter sw = new StringWriter();
UppercaseFilterWriter ufw = new UppercaseFilterWriter(sw);
HtmlEscapeFilterWriter hefw = new HtmlEscapeFilterWriter(ufw)) {

// 先HTML转义再转大写
hefw.write("Hello & Goodbye <test>");

System.out.println("链式过滤结果:");
System.out.println(sw.toString());

} catch (IOException e) {
e.printStackTrace();
}
}

回推字符输入流

PushbackReader 允许将读取的字符推回到流中以便后续再次读取它是 FilterReader 的子类

  • 支持回推已读取的字符
  • 可以设置回推缓冲区大小
  • 适合解析器编译器场景

⭐⭐ 回推数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public static void basicPushback() {
String text = "123ABC";

try (StringReader sr = new StringReader(text);
PushbackReader pr = new PushbackReader(sr, 10)) {

// 读取数字部分
StringBuilder numbers = new StringBuilder();
int ch;

while ((ch = pr.read()) != -1 && Character.isDigit(ch)) {
numbers.append((char) ch);
}

System.out.println("读取的数字: " + numbers);

// 将非数字字符推回流中
if (ch != -1) {
pr.unread(ch);
System.out.println("回推字符: " + (char) ch);
}

// 继续读取剩余内容
StringBuilder rest = new StringBuilder();
while ((ch = pr.read()) != -1) {
rest.append((char) ch);
}

System.out.println("剩余内容: " + rest);

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 解析数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public static void parseNumbers() {
String text = "100 -200 3.14 -5.67 999";

try (StringReader sr = new StringReader(text);
PushbackReader pr = new PushbackReader(sr, 5)) {

System.out.println("\n=== 数字解析 ===");
System.out.println("原文: " + text);
System.out.print("解析结果: ");

int ch;
while ((ch = pr.read()) != -1) {
if (ch == '-' || Character.isDigit(ch)) {
// 开始解析数字
StringBuilder num = new StringBuilder();
boolean isNegative = (ch == '-');
boolean hasDecimal = false;

if (isNegative) {
num.append((char) ch);
ch = pr.read();
}

// 解析整数部分
while (ch != -1 && Character.isDigit(ch)) {
num.append((char) ch);
ch = pr.read();
}

// 解析小数部分
if (ch == '.') {
num.append('.');
ch = pr.read();
hasDecimal = true;
while (ch != -1 && Character.isDigit(ch)) {
num.append((char) ch);
ch = pr.read();
}
}

System.out.print(num + " ");

// 将非数字字符推回除了空格
if (ch != -1 && !Character.isWhitespace(ch)) {
pr.unread(ch);
}
}
}
System.out.println();
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 解析 HTML 标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public static void parseHTMLTags() {
String html = "<html><body><h1>标题</h1><p>段落</p></body></html>";

try (StringReader sr = new StringReader(html);
PushbackReader pr = new PushbackReader(sr, 100)) {
int ch;
while ((ch = pr.read()) != -1) {
if (ch == '<') {
// 解析标签
StringBuilder tag = new StringBuilder();
tag.append('<');

while ((ch = pr.read()) != -1 && ch != '>') {
tag.append((char) ch);
}
tag.append('>');

System.out.println("找到标签: " + tag);

} else {
// 读取文本内容
StringBuilder text = new StringBuilder();
while (ch != -1 && ch != '<') {
text.append((char) ch);
ch = pr.read();
}

if (text.length() > 0) {
System.out.println("文本内容: " + text);
}

// 回推 '<' 字符让外层循环处理
if (ch == '<') {
pr.unread(ch);
}
}
}

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 解析 CSV 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public static void parseCSV() {
String csv = "张三,\"北京,朝阳区\",25,\"工程师,Java\"";

try (StringReader sr = new StringReader(csv);
PushbackReader pr = new PushbackReader(sr, 10)) {

System.out.println("\n=== CSV解析 ===");
System.out.println("原文: " + csv);
System.out.print("解析字段: ");

boolean inQuotes = false;
StringBuilder field = new StringBuilder();
int ch;

while ((ch = pr.read()) != -1) {
char c = (char) ch;

if (c == '"') {
inQuotes = !inQuotes;
continue;
}

if (c == ',' && !inQuotes) {
// 字段结束
System.out.print(field + " | ");
field.setLength(0);
} else {
field.append(c);
}
}

// 最后一个字段
System.out.println(field);

} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 表达式解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static void expressionParser() {
String expr = "10+20*30-40/5";

try (StringReader sr = new StringReader(expr);
PushbackReader pr = new PushbackReader(sr, 10)) {

System.out.println("\n=== 表达式解析 ===");
System.out.println("表达式: " + expr);
System.out.print("解析结果: ");

int ch;
while ((ch = pr.read()) != -1) {
char c = (char) ch;

if (Character.isDigit(c)) {
// 解析数字
StringBuilder num = new StringBuilder();
num.append(c);

// 读取后续数字
while ((ch = pr.read()) != -1 && Character.isDigit((char) ch)) {
num.append((char) ch);
}

System.out.print(num);

// 如果不是数字回推
if (ch != -1 && !Character.isDigit((char) ch)) {
pr.unread(ch);
}
} else if (c == '+' || c == '-' || c == '*' || c == '/') {
System.out.print(" " + c + " ");
}
}
System.out.println();

} catch (IOException e) {
e.printStackTrace();
}
}

打印字符输出流

PrintWriter 提供了方便的打印方法printprintlnprintf可以将各种数据类型格式化为文本输出它是 System.outSystem.err 的类型也是日志输出的常用类

  • 提供 printprintlnprintf 等便捷方法
  • 自动刷新可选
  • 不会抛出 IOException
  • 支持格式化输出
  • 可以包装任何 Writer 或 OutputStream

⭐⭐ 基本打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void basicPrinting() {
try (PrintWriter pw = new PrintWriter("basic_print.txt")) {
// print方法不换行
pw.print("Hello");
pw.print(" ");
pw.print("World");

// println方法换行
pw.println(); // 空行
pw.println("This is a new line");

// 打印各种类型
pw.println("String: " + "Java");
pw.println("Integer: " + 100);
pw.println("Double: " + 3.14159);
pw.println("Boolean: " + true);
pw.println("Character: " + 'A');
pw.println("Object: " + new Date());

System.out.println("基本打印完成");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}

⭐⭐ 格式化输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void formattedOutput() {
try (PrintWriter pw = new PrintWriter("formatted.txt")) {
// printf方法格式化输出
pw.printf("Hello, %s!%n", "World");
pw.printf("整数: %d, 浮点数: %.2f%n", 100, 3.14159);

// 格式化表格
pw.println("\n学生成绩表:");
pw.printf("%-10s %-10s %-10s%n", "姓名", "年龄", "成绩");
pw.printf("%-10s %-10d %-10.2f%n", "张三", 20, 85.5);
pw.printf("%-10s %-10d %-10.2f%n", "李四", 21, 92.0);
pw.printf("%-10s %-10d %-10.2f%n", "王五", 19, 78.5);

// 格式化日期
Date now = new Date();
pw.printf("当前时间: %tF %<tT%n", now);

// 格式化数字
pw.printf("千分位: %,d%n", 1234567);
pw.printf("科学计数法: %e%n", 12345.6789);

System.out.println("格式化输出完成");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}

⭐⭐ 日志记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public static void loggingExample() {
try (PrintWriter log = new PrintWriter(new BufferedWriter(new FileWriter("app.log")), true)) {
// 日志级别
String[] levels = {"INFO", "WARNING", "ERROR", "DEBUG"};
String[] messages = {
"Application started",
"Configuration loaded successfully",
"Database connection established",
"User logged in: admin",
"Warning: Low disk space",
"Error: File not found",
"Processing request #12345",
"Application shutdown"
};

for (int i = 0; i < messages.length; i++) {
String level = levels[i % levels.length];
String timestamp = String.format("%tF %<tT", new Date());

// 格式化日志条目
log.printf("[%s] [%s] %s%n", timestamp, level, messages[i]);

// 模拟日志产生间隔
Thread.sleep(100);
}

System.out.println("日志已写入 " + logFile);

} catch (IOException | InterruptedException e) {
e.printStackTrace();
}

// 显示日志内容
System.out.println("\n日志内容预览:");
try (BufferedReader br = new BufferedReader(new FileReader(logFile))) {
String line;
int count = 0;
while ((line = br.readLine()) != null && count < 5) {
System.out.println(line);
count++;
}
System.out.println("...");
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 生成报表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static void generateReport() {
String[][] salesData = {
{"产品", "Q1", "Q2", "Q3", "Q4", "总计"},
{"产品A", "1200", "1350", "1400", "1600", "5550"},
{"产品B", "800", "950", "1100", "1250", "4100"},
{"产品C", "500", "600", "700", "800", "2600"},
{"总计", "2500", "2900", "3200", "3650", "12250"}
};

try (PrintWriter pw = new PrintWriter(new FileWriter("sales_report.txt"))) {
// 报表标题
pw.println("=".repeat(70));
pw.printf("销售报表 - %tF%n", new Date());
pw.println("=".repeat(70));
pw.println();

// 表头
for (int i = 0; i < salesData[0].length; i++) {
pw.printf("%-12s", salesData[0][i]);
}
pw.println();
pw.println("-".repeat(70));

// 数据行
for (int i = 1; i < salesData.length; i++) {
for (int j = 0; j < salesData[i].length; j++) {
pw.printf("%-12s", salesData[i][j]);
}
pw.println();
}

pw.println("-".repeat(70));

// 统计信息
int total = Integer.parseInt(salesData[4][5]);
double avg = total / 4.0;
pw.printf("年度总销售额: %,d%n", total);
pw.printf("季度平均销售额: %,.2f%n", avg);

System.out.println("报表已生成: sales_report.txt");
} catch (IOException e) {
e.printStackTrace();
}
}

⭐⭐ 错误处理

1
2
3
4
5
6
7
8
9
10
11
12
public static void errorHandling() {
try (PrintWriter pw2 = new PrintWriter(new FileWriter("valid.txt"))) {
pw2.println("正常写入");
if (pw2.checkError()) {
System.out.println("写入错误");
} else {
System.out.println("写入成功");
}
} catch (IOException e) {
System.out.println("I/O错误: " + e.getMessage());
}
}

压缩流

可以将文件压缩为ZIPGZIPJAR包

ZipEntry 和 ZipFile

ZipEntryZipFile 分别是用于创建 ZIP 条目和读取 ZIP 内容的类java.util.zip 包中

⭐⭐ 属性方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
ZipEntry entry = new ZipEntry("docs/readme.txt");

// 获取/设置条目名称
String name = entry.getName(); // "docs/readme.txt"
entry.setName("new_name.txt");

// 获取/设置压缩大小
long compressedSize = entry.getCompressedSize();
entry.setCompressedSize(1024L);

// 获取/设置原始大小
long size = entry.getSize();
entry.setSize(2048L);

// 获取/设置压缩方法
int method = entry.getMethod(); // ZipEntry.STORED 或 ZipEntry.DEFLATED
entry.setMethod(ZipEntry.DEFLATED);

// 获取/设置最后修改时间
long time = entry.getTime();
entry.setTime(System.currentTimeMillis());

// 获取额外字段数据
byte[] extra = entry.getExtra();
entry.setExtra(extraBytes);

// 获取注释
String comment = entry.getComment();
entry.setComment("This is a compressed file");

// 是否为目录
boolean isDir = entry.isDirectory();

// 克隆条目
Object clone = entry.clone();

⭐⭐ CRC32 校验

1
2
3
4
5
6
7
ZipEntry entry = new ZipEntry("data.txt");
entry.setMethod(ZipEntry.STORED); // 使用存储方式必须设置CRC

// 计算CRC32值
CRC32 crc = new CRC32();
crc.update(dataBytes);
entry.setCrc(crc.getValue());

⭐⭐ 压缩方法

1
2
3
4
5
6
7
8
9
10
11
12
// 不压缩直接存储
public static final int STORED = 0;

// 使用 DEFLATE 算法压缩
public static final int DEFLATED = 8;

// 使用示例
ZipEntry entry = new ZipEntry("small.txt");
entry.setMethod(ZipEntry.STORED); // 小文件不压缩

ZipEntry entry2 = new ZipEntry("large.txt");
entry2.setMethod(ZipEntry.DEFLATED); // 大文件压缩

⭐⭐ 获取条目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try (ZipFile zipFile = new ZipFile("archive.zip")) {
// 1. 通过名称获取单个条目
ZipEntry entry = zipFile.getEntry("docs/readme.txt");

// 2. 获取所有条目的枚举
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry e = entries.nextElement();
System.out.println(e.getName());
}

// 3. 获取条目数量
int size = zipFile.size();
System.out.println("ZIP文件包含 " + size + " 个条目");
}

⭐⭐ 读取内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try (ZipFile zipFile = new ZipFile("archive.zip")) {
ZipEntry entry = zipFile.getEntry("data.txt");
if (entry != null && !entry.isDirectory()) {
// 获取输入流读取内容
try (InputStream is = zipFile.getInputStream(entry)) {
byte[] buffer = new byte[1024];
int len;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
String content = baos.toString("UTF-8");
System.out.println(content);
}
}
}

⭐⭐ 其他方法

1
2
3
4
5
6
7
8
9
10
11
try (ZipFile zipFile = new ZipFile("archive.zip")) {
// 获取注释
String comment = zipFile.getComment();
System.out.println("ZIP注释: " + comment);

// 获取文件名
String name = zipFile.getName();

// 关闭文件try-with-resources 自动处理
// zipFile.close(); // 自动关闭
}

⭐⭐ 从 ZIP 中提取所有文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static void extractAll(String zipPath, String outputDir) throws IOException {
try (ZipFile zipFile = new ZipFile(zipPath, Charset.forName("UTF-8"))) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
int extracted = 0;

while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String outputPath = outputDir + File.separator + entry.getName();

if (entry.isDirectory()) {
Files.createDirectories(Paths.get(outputPath));
} else {
// 确保父目录存在
Path outputFile = Paths.get(outputPath);
Files.createDirectories(outputFile.getParent());

// 提取文件
try (InputStream is = zipFile.getInputStream(entry);
OutputStream os = new FileOutputStream(outputFile.toFile())) {

byte[] buffer = new byte[8192];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}

extracted++;
if (extracted % 100 == 0) {
System.out.printf("已提取 %d 个文件...%n", extracted);
}
}
}
}
System.out.printf("提取完成共提取 %d 个文件到: %s%n", extracted, outputDir);
}
}

JarEntry 和 JarFile

JarEntryJarFile它们是专门用于处理 JARJava Archive文件的类

JAR 文件本质上是带有特殊清单文件MANIFEST.MF的 ZIP 文件JarFileJarEntry 继承自 ZipFileZipEntry并增加了对 JAR 特有功能如清单签名等的支持

⭐⭐ 读取条目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;

public class JarFileBasics {

/**
* 打开和读取 JAR 文件
*/
public static void openJarFile(String jarPath) throws IOException {
// 基本方式打开
try (JarFile jarFile = new JarFile(jarPath)) {
System.out.println("JAR 文件: " + jarFile.getName());
System.out.println("条目总数: " + jarFile.size());

// 获取清单文件
Manifest manifest = jarFile.getManifest();
if (manifest != null) {
System.out.println("包含清单文件");
printManifest(manifest);
}
}
}

/**
* 遍历 JAR 文件内容
*/
public static void listJarContents(String jarPath) throws IOException {
try (JarFile jarFile = new JarFile(jarPath)) {
System.out.println("=== JAR 文件内容 ===");
System.out.println("文件: " + jarFile.getName());
System.out.println("-".repeat(60));

Enumeration<JarEntry> entries = jarFile.entries();
int fileCount = 0;
int dirCount = 0;

while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();

if (entry.isDirectory()) {
dirCount++;
System.out.printf("[目录] %s%n", entry.getName());
} else {
fileCount++;
System.out.printf("[文件] %s (%,d bytes)%n",
entry.getName(), entry.getSize());
}
}

System.out.println("-".repeat(60));
System.out.printf("总计: %d 个文件, %d 个目录%n", fileCount, dirCount);
}
}

/**
* 从 JAR 中读取特定条目
*/
public static void readJarEntry(String jarPath, String entryName)
throws IOException {

try (JarFile jarFile = new JarFile(jarPath)) {
JarEntry entry = jarFile.getJarEntry(entryName);

if (entry == null) {
System.out.println("未找到条目: " + entryName);
return;
}

System.out.println("读取条目: " + entryName);
System.out.println("大小: " + entry.getSize() + " bytes");

// 读取内容
try (InputStream is = jarFile.getInputStream(entry);
BufferedReader reader = new BufferedReader(
new InputStreamReader(is))) {

String line;
int lineNum = 0;
while ((line = reader.readLine()) != null && lineNum < 20) {
System.out.printf("%4d: %s%n", ++lineNum, line);
}

if (reader.readLine() != null) {
System.out.println("... (内容已截断)");
}
}
}
}

/**
* 打印清单内容
*/
private static void printManifest(Manifest manifest) {
Attributes attributes = manifest.getMainAttributes();
System.out.println("\n=== 清单内容 ===");

for (Object key : attributes.keySet()) {
System.out.printf("%s: %s%n", key, attributes.get(key));
}

// 打印条目属性
Map<String, Attributes> entries = manifest.getEntries();
if (!entries.isEmpty()) {
System.out.println("\n=== 条目属性 ===");
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
System.out.println("条目: " + entry.getKey());
for (Object key : entry.getValue().keySet()) {
System.out.printf(" %s: %s%n", key, entry.getValue().get(key));
}
}
}
}

public static void main(String[] args) {
try {
String jarPath = "example.jar";
openJarFile(jarPath);
listJarContents(jarPath);
readJarEntry(jarPath, "META-INF/MANIFEST.MF");

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 创建 JAR 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import java.io.*;
import java.util.jar.*;
import java.util.*;

public class JarFileCreator {

/**
* 创建简单的 JAR 文件
*/
public static void createSimpleJar(String jarPath, String[] files)
throws IOException {

try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(jarPath))) {

// 创建清单
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attributes.put(Attributes.Name.CREATED_BY, "Java Application");

// 写入清单
JarEntry manifestEntry = new JarEntry("META-INF/MANIFEST.MF");
jos.putNextEntry(manifestEntry);
manifest.write(jos);
jos.closeEntry();

// 添加文件
for (String filePath : files) {
File file = new File(filePath);
if (!file.exists()) continue;

JarEntry entry = new JarEntry(file.getName());
entry.setTime(file.lastModified());
jos.putNextEntry(entry);

try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}
}

jos.closeEntry();
System.out.println("添加文件: " + file.getName());
}

System.out.println("JAR 创建成功: " + jarPath);
}
}

/**
* 创建可执行的 JAR 文件
*/
public static void createExecutableJar(String jarPath, String mainClass,
String[] classFiles) throws IOException {

try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(jarPath))) {

// 创建清单
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attributes.put(Attributes.Name.MAIN_CLASS, mainClass);
attributes.put(Attributes.Name.CLASS_PATH, ".");

// 写入清单
JarEntry manifestEntry = new JarEntry("META-INF/MANIFEST.MF");
jos.putNextEntry(manifestEntry);
manifest.write(jos);
jos.closeEntry();

// 添加类文件
for (String classFile : classFiles) {
File file = new File(classFile);
String entryName = classFile.replace(File.separator, "/");

JarEntry entry = new JarEntry(entryName);
entry.setTime(file.lastModified());
jos.putNextEntry(entry);

try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}
}

jos.closeEntry();
System.out.println("添加类: " + entryName);
}

System.out.println("可执行 JAR 创建成功: " + jarPath);
System.out.println("运行命令: java -jar " + jarPath);
}
}

/**
* 创建带资源文件的 JAR
*/
public static void createJarWithResources(String jarPath,
Map<String, byte[]> resources)
throws IOException {

try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(jarPath))) {

// 创建清单
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");

jos.putNextEntry(new JarEntry("META-INF/MANIFEST.MF"));
manifest.write(jos);
jos.closeEntry();

// 添加资源
for (Map.Entry<String, byte[]> resource : resources.entrySet()) {
JarEntry entry = new JarEntry(resource.getKey());
jos.putNextEntry(entry);
jos.write(resource.getValue());
jos.closeEntry();

System.out.println("添加资源: " + resource.getKey() +
" (" + resource.getValue().length + " bytes)");
}
}
}

/**
* 从目录创建 JAR
*/
public static void createJarFromDirectory(String sourceDir, String jarPath)
throws IOException {

try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(jarPath))) {

Path sourcePath = Paths.get(sourceDir);

// 遍历目录
Files.walk(sourcePath)
.filter(path -> !Files.isDirectory(path))
.forEach(path -> {
try {
String entryName = sourcePath.relativize(path).toString()
.replace("\\", "/");

JarEntry entry = new JarEntry(entryName);
entry.setTime(Files.getLastModifiedTime(path).toMillis());

jos.putNextEntry(entry);
Files.copy(path, jos);
jos.closeEntry();

System.out.println("添加: " + entryName);
} catch (IOException e) {
e.printStackTrace();
}
});

System.out.println("JAR 创建完成: " + jarPath);
}
}

public static void main(String[] args) {
try {
// 创建简单 JAR
String[] files = {"README.txt", "config.properties"};
createSimpleJar("simple.jar", files);

// 创建可执行 JAR
String[] classes = {"com/example/Main.class", "com/example/Utils.class"};
createExecutableJar("app.jar", "com.example.Main", classes);

// 从目录创建
createJarFromDirectory("build/classes", "output.jar");

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 修改和更新 JAR 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;

public class JarFileModifier {

/**
* 更新 JAR 中的清单文件
*/
public static void updateManifest(String jarPath, Map<String, String> attributes)
throws IOException {

File tempFile = File.createTempFile("jar", ".tmp");

try (JarFile jarFile = new JarFile(jarPath);
JarOutputStream jos = new JarOutputStream(
new FileOutputStream(tempFile))) {

// 复制现有条目
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();

// 跳过旧的清单文件
if (entry.getName().equals("META-INF/MANIFEST.MF")) {
continue;
}

jos.putNextEntry(new JarEntry(entry.getName()));
try (InputStream is = jarFile.getInputStream(entry)) {
byte[] buffer = new byte[8192];
int len;
while ((len = is.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}
}
jos.closeEntry();
}

// 创建新的清单
Manifest manifest = jarFile.getManifest();
if (manifest == null) {
manifest = new Manifest();
}

Attributes mainAttributes = manifest.getMainAttributes();
for (Map.Entry<String, String> attr : attributes.entrySet()) {
mainAttributes.put(new Attributes.Name(attr.getKey()), attr.getValue());
}

// 写入新清单
JarEntry manifestEntry = new JarEntry("META-INF/MANIFEST.MF");
jos.putNextEntry(manifestEntry);
manifest.write(jos);
jos.closeEntry();
}

// 替换原文件
Files.move(tempFile.toPath(), Paths.get(jarPath),
StandardCopyOption.REPLACE_EXISTING);

System.out.println("清单更新成功");
}

/**
* 向 JAR 中添加文件
*/
public static void addFilesToJar(String jarPath, Map<String, byte[]> newFiles)
throws IOException {

File tempFile = File.createTempFile("jar", ".tmp");

try (JarFile jarFile = new JarFile(jarPath);
JarOutputStream jos = new JarOutputStream(
new FileOutputStream(tempFile))) {

// 复制现有文件
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
jos.putNextEntry(new JarEntry(entry.getName()));

try (InputStream is = jarFile.getInputStream(entry)) {
byte[] buffer = new byte[8192];
int len;
while ((len = is.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}
}
jos.closeEntry();
}

// 添加新文件
for (Map.Entry<String, byte[]> newFile : newFiles.entrySet()) {
JarEntry entry = new JarEntry(newFile.getKey());
jos.putNextEntry(entry);
jos.write(newFile.getValue());
jos.closeEntry();
System.out.println("添加: " + newFile.getKey());
}
}

// 替换原文件
Files.move(tempFile.toPath(), Paths.get(jarPath),
StandardCopyOption.REPLACE_EXISTING);
}

/**
* 从 JAR 中删除文件
*/
public static void removeFilesFromJar(String jarPath, List<String> filesToRemove)
throws IOException {

File tempFile = File.createTempFile("jar", ".tmp");

try (JarFile jarFile = new JarFile(jarPath);
JarOutputStream jos = new JarOutputStream(
new FileOutputStream(tempFile))) {

Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();

// 跳过要删除的文件
if (filesToRemove.contains(entryName)) {
System.out.println("删除: " + entryName);
continue;
}

jos.putNextEntry(new JarEntry(entryName));
try (InputStream is = jarFile.getInputStream(entry)) {
byte[] buffer = new byte[8192];
int len;
while ((len = is.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}
}
jos.closeEntry();
}
}

Files.move(tempFile.toPath(), Paths.get(jarPath),
StandardCopyOption.REPLACE_EXISTING);

System.out.println("文件删除完成");
}

public static void main(String[] args) {
try {
String jarPath = "app.jar";

// 更新清单
Map<String, String> attrs = new HashMap<>();
attrs.put("Implementation-Version", "2.0");
attrs.put("Built-By", "Developer");
updateManifest(jarPath, attrs);

// 添加文件
Map<String, byte[]> newFiles = new HashMap<>();
newFiles.put("config/new.properties",
"version=2.0\ncreated=2024-01-01".getBytes());
addFilesToJar(jarPath, newFiles);

// 删除文件
List<String> toRemove = Arrays.asList("old-config.properties");
removeFilesFromJar(jarPath, toRemove);

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 高级 JAR 文件操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;

public class AdvancedJarFileOperations {

/**
* 读取 JAR 的签名信息
*/
public static void readJarSignatures(String jarPath) throws IOException {
try (JarFile jarFile = new JarFile(jarPath, true)) { // verify = true 验证签名

Enumeration<JarEntry> entries = jarFile.entries();

System.out.println("=== 签名验证 ===");
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();

// 检查签名相关文件
String name = entry.getName();
if (name.startsWith("META-INF/") &&
(name.endsWith(".SF") || name.endsWith(".DSA") ||
name.endsWith(".RSA") || name.endsWith(".EC"))) {

System.out.println("签名文件: " + name);

// 读取签名文件内容
try (InputStream is = jarFile.getInputStream(entry);
BufferedReader reader = new BufferedReader(
new InputStreamReader(is))) {

String line;
while ((line = reader.readLine()) != null) {
if (line.contains("SHA-256-Digest") ||
line.contains("SHA1-Digest")) {
System.out.println(" " + line);
}
}
}
}
}
}
}

/**
* 验证 JAR 文件的完整性
*/
public static void verifyJarIntegrity(String jarPath) throws IOException {
try (JarFile jarFile = new JarFile(jarPath, true)) {
System.out.println("JAR 文件签名验证: " +
(jarFile.isSigned() ? "已签名" : "未签名"));

Enumeration<JarEntry> entries = jarFile.entries();
boolean allValid = true;

while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();

if (!entry.isDirectory() && !entry.getName().startsWith("META-INF/")) {
try (InputStream is = jarFile.getInputStream(entry)) {
// 读取并验证如果 JAR 已签名会自动验证
byte[] buffer = new byte[8192];
while (is.read(buffer) != -1) {
// 读取过程中会自动验证签名
}
System.out.printf("✓ 验证通过: %s%n", entry.getName());
} catch (SecurityException e) {
System.err.printf("✗ 签名验证失败: %s - %s%n",
entry.getName(), e.getMessage());
allValid = false;
}
}
}

if (allValid) {
System.out.println("所有条目验证通过");
} else {
System.err.println("JAR 文件包含未通过验证的条目");
}
}
}

/**
* 获取 JAR 的类路径信息
*/
public static void getClassPathInfo(String jarPath) throws IOException {
try (JarFile jarFile = new JarFile(jarPath)) {
Manifest manifest = jarFile.getManifest();

if (manifest != null) {
Attributes attributes = manifest.getMainAttributes();

// 获取 Class-Path
String classPath = attributes.getValue("Class-Path");
if (classPath != null) {
System.out.println("Class-Path: " + classPath);
String[] paths = classPath.split(" ");
for (String path : paths) {
System.out.println(" " + path.trim());
}
}

// 获取 Main-Class
String mainClass = attributes.getValue("Main-Class");
if (mainClass != null) {
System.out.println("Main-Class: " + mainClass);
}

// 获取其他属性
String createdBy = attributes.getValue("Created-By");
if (createdBy != null) {
System.out.println("Created-By: " + createdBy);
}

String implementationVersion = attributes.getValue("Implementation-Version");
if (implementationVersion != null) {
System.out.println("Version: " + implementationVersion);
}
}
}
}

/**
* 搜索 JAR 中的类
*/
public static List<String> searchClasses(String jarPath, String className)
throws IOException {

List<String> results = new ArrayList<>();
String searchPattern = className.replace('.', '/');

try (JarFile jarFile = new JarFile(jarPath)) {
Enumeration<JarEntry> entries = jarFile.entries();

while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();

if (name.endsWith(".class") && name.contains(searchPattern)) {
String classFullName = name.replace('/', '.')
.replace(".class", "");
results.add(classFullName);
}
}
}

return results;
}

public static void main(String[] args) {
try {
String jarPath = "app.jar";

getClassPathInfo(jarPath);
verifyJarIntegrity(jarPath);
readJarSignatures(jarPath);

List<String> classes = searchClasses(jarPath, "com.example");
System.out.println("\n找到的类:");
for (String clazz : classes) {
System.out.println(" " + clazz);
}

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ JAR 文件分析和诊断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;

public class JarFileAnalyzer {

/**
* 分析 JAR 文件结构
*/
public static void analyzeJarStructure(String jarPath) throws IOException {
System.out.println("=== JAR 文件分析报告 ===");
System.out.println("文件: " + jarPath);

try (JarFile jarFile = new JarFile(jarPath)) {
// 基本信息
System.out.println("\n--- 基本信息 ---");
System.out.println("大小: " + new File(jarPath).length() + " bytes");
System.out.println("条目总数: " + jarFile.size());
System.out.println("是否签名: " + (jarFile.isSigned() ? "是" : "否"));

// 清单分析
Manifest manifest = jarFile.getManifest();
if (manifest != null) {
System.out.println("\n--- 清单分析 ---");
Attributes attrs = manifest.getMainAttributes();
for (Object key : attrs.keySet()) {
System.out.printf("%-20s: %s%n", key, attrs.get(key));
}
}

// 内容分类统计
System.out.println("\n--- 内容统计 ---");
int classCount = 0;
int resourceCount = 0;
int metaInfCount = 0;
long totalSize = 0;

Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();

if (name.endsWith(".class")) {
classCount++;
} else if (name.startsWith("META-INF/")) {
metaInfCount++;
} else {
resourceCount++;
}

totalSize += entry.getSize();
}

System.out.println("类文件: " + classCount);
System.out.println("资源文件: " + resourceCount);
System.out.println("META-INF 文件: " + metaInfCount);
System.out.println("总大小: " + totalSize + " bytes");

// 包结构分析
System.out.println("\n--- 包结构 ---");
Set<String> packages = new TreeSet<>();
entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (name.endsWith(".class")) {
String packageName = name.substring(0, name.lastIndexOf('/'));
packages.add(packageName.replace('/', '.'));
}
}

for (String pkg : packages) {
System.out.println(" " + pkg);
}
}
}

/**
* 检测重复的类
*/
public static void findDuplicateClasses(String jarPath) throws IOException {
System.out.println("\n=== 重复类检测 ===");

Map<String, List<JarEntry>> classMap = new HashMap<>();

try (JarFile jarFile = new JarFile(jarPath)) {
Enumeration<JarEntry> entries = jarFile.entries();

while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();

if (name.endsWith(".class")) {
classMap.computeIfAbsent(name, k -> new ArrayList<>()).add(entry);
}
}
}

boolean hasDuplicates = false;
for (Map.Entry<String, List<JarEntry>> entry : classMap.entrySet()) {
if (entry.getValue().size() > 1) {
System.out.println("重复类: " + entry.getKey());
hasDuplicates = true;
}
}

if (!hasDuplicates) {
System.out.println("未发现重复类");
}
}

/**
* 检查 JAR 兼容性
*/
public static void checkCompatibility(String jarPath) throws IOException {
System.out.println("\n=== 兼容性检查 ===");

try (JarFile jarFile = new JarFile(jarPath)) {
Manifest manifest = jarFile.getManifest();

if (manifest != null) {
Attributes attrs = manifest.getMainAttributes();

// 检查 Java 版本
String createdBy = attrs.getValue("Created-By");
if (createdBy != null && createdBy.contains("javac")) {
System.out.println("编译版本: " + createdBy);
}

// 检查依赖
String classPath = attrs.getValue("Class-Path");
if (classPath != null) {
System.out.println("依赖项:");
for (String dep : classPath.split(" ")) {
System.out.println(" " + dep.trim());
}
}
}

// 检查 Java 版本要求
Enumeration<JarEntry> entries = jarFile.entries();
Set<Integer> versions = new HashSet<>();

while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".class")) {
try (InputStream is = jarFile.getInputStream(entry)) {
byte[] magic = new byte[8];
if (is.read(magic) == 8) {
// 检查 class 文件版本
int majorVersion = ((magic[6] & 0xFF) << 8) | (magic[7] & 0xFF);
versions.add(majorVersion);
}
}
}
}

if (!versions.isEmpty()) {
System.out.println("Class 文件版本: " + versions);
for (int version : versions) {
System.out.println(" 需要 Java " + getJavaVersion(version));
}
}
}
}

private static String getJavaVersion(int majorVersion) {
switch (majorVersion) {
case 52: return "8";
case 53: return "9";
case 54: return "10";
case 55: return "11";
case 56: return "12";
case 57: return "13";
case 58: return "14";
case 59: return "15";
case 60: return "16";
case 61: return "17";
case 62: return "18";
case 63: return "19";
case 64: return "20";
default: return "Unknown (" + majorVersion + ")";
}
}

public static void main(String[] args) {
try {
analyzeJarStructure("app.jar");
findDuplicateClasses("app.jar");
checkCompatibility("app.jar");

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 高效读取 JAR 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class JarFileOptimization {

/**
* 缓存 JarFile 实例
*/
public static class JarFileCache {
private static final Map<String, JarFile> cache = new HashMap<>();

public static synchronized JarFile getJarFile(String path) throws IOException {
if (!cache.containsKey(path)) {
cache.put(path, new JarFile(path));
}
return cache.get(path);
}

public static synchronized void closeAll() throws IOException {
for (JarFile jarFile : cache.values()) {
jarFile.close();
}
cache.clear();
}
}

/**
* 批量读取 JAR 中的多个文件
*/
public static Map<String, byte[]> batchReadEntries(String jarPath,
List<String> entryNames)
throws IOException {

Map<String, byte[]> result = new HashMap<>();

try (JarFile jarFile = new JarFile(jarPath)) {
for (String entryName : entryNames) {
JarEntry entry = jarFile.getJarEntry(entryName);
if (entry != null && !entry.isDirectory()) {
try (InputStream is = jarFile.getInputStream(entry);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

byte[] buffer = new byte[8192];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}

result.put(entryName, baos.toByteArray());
}
}
}
}

return result;
}

/**
* 使用内存映射读取大 JAR
*/
public static void readLargeJarWithMapping(String jarPath) throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(jarPath, "r");
FileChannel channel = raf.getChannel()) {

// 映射整个文件
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY, 0, channel.size());

// 注意JarFile 不支持直接使用内存映射这里仅作示例
System.out.println("JAR 文件大小: " + channel.size() + " bytes");
}
}
}

ZIP 压缩与解压

ZipInputStreamZipOutputStream它们是专门用于处理 ZIP 格式的压缩流ZipInputStream 是用于读取 ZIP 文件内容的输入流采用顺序读取的方式ZipOutputStream 是用于创建 ZIP 文件的输出流支持压缩和存储

⭐⭐ 读取 ZIP 文件并提取所有内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static void extractZip(String zipPath, String outputDir) throws IOException {
try (ZipInputStream zis = new ZipInputStream(
new FileInputStream(zipPath),
StandardCharsets.UTF_8)) {

ZipEntry entry;
byte[] buffer = new byte[8192];
int count = 0;

while ((entry = zis.getNextEntry()) != null) {
String entryName = entry.getName();
Path outputPath = Paths.get(outputDir, entryName);

if (entry.isDirectory()) {
// 创建目录
Files.createDirectories(outputPath);
System.out.println("创建目录: " + entryName);
} else {
// 确保父目录存在
Files.createDirectories(outputPath.getParent());

// 写入文件
try (FileOutputStream fos = new FileOutputStream(outputPath.toFile())) {
int len;
while ((len = zis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}

System.out.printf("提取文件: %s (%d bytes)%n",
entryName, entry.getSize());
count++;
}

zis.closeEntry();
}

System.out.println("提取完成共 " + count + " 个文件");
}
}

⭐⭐ 读取 ZIP 中指定文件的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static byte[] readFileFromZip(String zipPath, String targetFileName) throws IOException {
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipPath))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (!entry.isDirectory() && entry.getName().equals(targetFileName)) {
// 找到目标文件
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
int len;
while ((len = zis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
zis.closeEntry();
return baos.toByteArray();
}
zis.closeEntry();
}
}
return null; // 未找到文件
}

⭐⭐ 逐行读取 ZIP 中的文本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void readTextFileLineByLine(String zipPath, String entryName) throws IOException {
try (ZipInputStream zis = new ZipInputStream( new FileInputStream(zipPath))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (entry.getName().equals(entryName) && !entry.isDirectory()) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(zis, StandardCharsets.UTF_8))) {
String line;
int lineNum = 0;
while ((line = reader.readLine()) != null) {
System.out.printf("%d: %s%n", ++lineNum, line);
}
}
zis.closeEntry();
break;
}
zis.closeEntry();
}
}
}

⭐⭐ 处理大文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import java.io.*;
import java.util.zip.*;

public class LargeZipProcessor {

/**
* 流式处理大 ZIP 文件避免内存溢出
*/
public static void processLargeZip(String zipPath, EntryProcessor processor) throws IOException {
try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipPath)))) {
ZipEntry entry;
byte[] buffer = new byte[8192];

while ((entry = zis.getNextEntry()) != null) {
if (!entry.isDirectory()) {
// 处理条目传递流而不是整个内容
processor.process(entry, zis);
}
zis.closeEntry();
}
}
}

@FunctionalInterface
public interface EntryProcessor {
void process(ZipEntry entry, InputStream is) throws IOException;
}

/**
* 统计 ZIP 中所有文件的总大小
*/
public static long calculateTotalSize(String zipPath) throws IOException {
long totalSize = 0;
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipPath))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (!entry.isDirectory()) {
totalSize += entry.getSize();
}
zis.closeEntry();
}
}
return totalSize;
}

/**
* 搜索 ZIP 中包含特定内容的文件
*/
public static void searchContent(String zipPath, String searchText) throws IOException {
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipPath))) {
ZipEntry entry;
byte[] buffer = new byte[8192];
while ((entry = zis.getNextEntry()) != null) {
if (!entry.isDirectory()) {
// 读取内容并搜索
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len;
while ((len = zis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}

String content = baos.toString("UTF-8");
if (content.contains(searchText)) {
System.out.println("找到匹配文件: " + entry.getName());
System.out.println("匹配文本位置: " + content.indexOf(searchText));
}
}
zis.closeEntry();
}
}
}

public static void main(String[] args) {
try {
// 统计总大小
long totalSize = calculateTotalSize("large.zip");
System.out.printf("总大小: %.2f MB%n", totalSize / (1024.0 * 1024));
// 搜索内容
searchContent("archive.zip", "important");
} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 压缩单个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void compressFile(String filePath, String zipPath) throws IOException {
try (ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream(zipPath));
FileInputStream fis = new FileInputStream(filePath)) {

File file = new File(filePath);
ZipEntry entry = new ZipEntry(file.getName());

// 设置条目属性
entry.setTime(file.lastModified());
entry.setSize(file.length());

zos.putNextEntry(entry);

// 写入内容
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
zos.write(buffer, 0, len);
}

zos.closeEntry();
System.out.println("压缩完成: " + filePath);
}
}

⭐⭐ 压缩目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void compressDirectory(String dirPath, String zipPath) throws IOException {
try (ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream(zipPath))) {

Path sourceDir = Paths.get(dirPath);
Files.walk(sourceDir)
.filter(path -> !Files.isDirectory(path))
.forEach(path -> {
try {
// 获取相对路径
String entryName = sourceDir.relativize(path).toString()
.replace("\\", "/");

ZipEntry entry = new ZipEntry(entryName);
entry.setTime(Files.getLastModifiedTime(path).toMillis());

zos.putNextEntry(entry);
Files.copy(path, zos);
zos.closeEntry();

System.out.println("添加文件: " + entryName);
} catch (IOException e) {
e.printStackTrace();
}
});

System.out.println("目录压缩完成: " + zipPath);
}
}

⭐⭐ 压缩多个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public static void compressFiles(List<String> filePaths, String zipPath) throws IOException {
try (ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream(zipPath))) {

for (String filePath : filePaths) {
File file = new File(filePath);
if (!file.exists()) {
System.err.println("文件不存在: " + filePath);
continue;
}

try (FileInputStream fis = new FileInputStream(file)) {
ZipEntry entry = new ZipEntry(file.getName());
entry.setTime(file.lastModified());
entry.setSize(file.length());

// 对小文件使用 STORED 模式
if (file.length() < 1024) {
entry.setMethod(ZipEntry.STORED);
entry.setCompressedSize(file.length());

// 计算 CRC32
CRC32 crc = new CRC32();
byte[] buffer = new byte[8192];
int len;
try (FileInputStream fis2 = new FileInputStream(file)) {
while ((len = fis2.read(buffer)) != -1) {
crc.update(buffer, 0, len);
}
}
entry.setCrc(crc.getValue());
} else {
entry.setMethod(ZipEntry.DEFLATED);
}

zos.putNextEntry(entry);

// 复制文件内容
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
zos.write(buffer, 0, len);
}

zos.closeEntry();
System.out.println("添加文件: " + file.getName() +
" (" + file.length() + " bytes)");
}
}
}
}

⭐⭐ 带过滤器的压缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public static void compressWithFilter(String dirPath, String zipPath, FileFilter filter) throws IOException {
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipPath))) {
// 设置压缩级别
zos.setLevel(Deflater.BEST_COMPRESSION);

Path sourceDir = Paths.get(dirPath);
Files.walk(sourceDir)
.filter(path -> !Files.isDirectory(path))
.filter(path -> filter.accept(path.toFile()))
.forEach(path -> {
try {
String entryName = sourceDir.relativize(path).toString()
.replace("\\", "/");

ZipEntry entry = new ZipEntry(entryName);
entry.setTime(Files.getLastModifiedTime(path).toMillis());

// 根据文件类型设置不同的压缩级别
if (entryName.endsWith(".jpg") || entryName.endsWith(".png")) {
// 图片文件使用 STORED已压缩
entry.setMethod(ZipEntry.STORED);
entry.setSize(Files.size(path));
entry.setCompressedSize(Files.size(path));

// 计算 CRC
CRC32 crc = new CRC32();
Files.copy(path, crc);
entry.setCrc(crc.getValue());
}

zos.putNextEntry(entry);
Files.copy(path, zos);
zos.closeEntry();
} catch (IOException e) {
e.printStackTrace();
}
});
}
}

⭐⭐ 设置压缩参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void configureCompression(String zipPath) throws IOException {
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipPath))) {
// 设置压缩级别0-9
// 0: 不压缩9: 最高压缩比
zos.setLevel(Deflater.BEST_COMPRESSION);

// 设置压缩策略
// Deflater.DEFAULT_STRATEGY: 默认
// Deflater.FILTERED: 适用于大部分数据已压缩的文件
// Deflater.HUFFMAN_ONLY: 仅使用 Huffman 编码
Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
deflater.setStrategy(Deflater.DEFAULT_STRATEGY);

// 设置注释
zos.setComment("Created at: " + new Date());

// 设置方法
zos.setMethod(ZipOutputStream.DEFLATED);
}
}

⭐⭐ 使用缓冲流提高性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void optimizedCompression(String sourcePath, String zipPath) throws IOException {
try (ZipOutputStream zos = new ZipOutputStream(
new BufferedOutputStream(new FileOutputStream(zipPath), 65536));
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(sourcePath), 65536)) {

zos.setLevel(Deflater.BEST_SPEED); // 速度优先
zos.setMethod(ZipOutputStream.DEFLATED);

ZipEntry entry = new ZipEntry(new File(sourcePath).getName());
zos.putNextEntry(entry);

byte[] buffer = new byte[65536]; // 64KB 缓冲区
int len;
while ((len = bis.read(buffer)) != -1) {
zos.write(buffer, 0, len);
}

zos.closeEntry();
}
}

⭐⭐ 并行压缩多个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public static void parallelCompress(List<String> files, String zipPath) throws IOException, InterruptedException {
// 先创建临时文件
List<Path> tempFiles = new ArrayList<>();
try {
// 并行压缩每个文件到临时文件
files.parallelStream().forEach(file -> {
try {
Path tempFile = Files.createTempFile("zip_temp_", ".tmp");
try (ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream(tempFile.toFile()));
FileInputStream fis = new FileInputStream(file)) {

ZipEntry entry = new ZipEntry(new File(file).getName());
zos.putNextEntry(entry);

byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
zos.write(buffer, 0, len);
}

zos.closeEntry();
}
synchronized (tempFiles) {
tempFiles.add(tempFile);
}
} catch (IOException e) {
e.printStackTrace();
}
});

// 合并临时文件到最终 ZIP
try (ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream(zipPath))) {

for (Path tempFile : tempFiles) {
try (FileInputStream fis = new FileInputStream(tempFile.toFile())) {
// 这里需要解析临时 ZIP 的内容并合并
// 简化示例实际需要更复杂的处理
}
}
}

} finally {
// 清理临时文件
for (Path tempFile : tempFiles) {
Files.deleteIfExists(tempFile);
}
}
}

⭐⭐ 使用内存映射文件处理大文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void compressLargeFile(String filePath, String zipPath) throws IOException {
Path path = Paths.get(filePath);
long fileSize = Files.size(path);

try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);
ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream(zipPath))) {

ZipEntry entry = new ZipEntry(path.getFileName().toString());
entry.setSize(fileSize);
zos.putNextEntry(entry);

// 使用内存映射
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY, 0, fileSize);

byte[] bytes = new byte[8192];
while (buffer.hasRemaining()) {
int remaining = buffer.remaining();
int readSize = Math.min(bytes.length, remaining);
buffer.get(bytes, 0, readSize);
zos.write(bytes, 0, readSize);
}

zos.closeEntry();
}
}

⭐⭐ 大文件处理避免内存溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void processLargeZip(String zipPath) throws IOException {
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipPath))) {
ZipEntry entry;
byte[] buffer = new byte[8192];
while ((entry = zis.getNextEntry()) != null) {
if (!entry.isDirectory()) {
// 直接处理流不要全部读入内存
try (OutputStream os = new FileOutputStream(entry.getName())) {
int len;
while ((len = zis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
}
}
zis.closeEntry();
}
}
}

⭐⭐ 处理中文文件名

1
2
3
4
5
6
7
8
9
10
// 使用正确的字符编码
ZipInputStream zis = new ZipInputStream(
new FileInputStream("archive.zip"),
Charset.forName("GBK") // 或 "UTF-8"
);

ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream("output.zip"),
StandardCharsets.UTF_8
);

GZIP 压缩与解压

GZIPInputStreamGZIPOutputStream它们是专门用于处理 GZIP 格式的压缩流GZIP 是 GNU ZIP 的缩写是一种单一文件的压缩格式与 ZIP 不同

  • GZIP 只能压缩单个文件不能包含多个文件
  • 通常与 tar 配合使用tar.gz 或 .tgz
  • 压缩率通常比 ZIP 更高
  • 广泛用于 Linux/Unix 环境

⭐⭐ 压缩数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import java.io.*;
import java.util.zip.*;

public class GZIPOutputStreamBasic {
/**
* 压缩单个文件为 GZIP 格式
*/
public static void compressFile(String sourceFile, String gzipFile)
throws IOException {

try (FileInputStream fis = new FileInputStream(sourceFile);
GZIPOutputStream gzos = new GZIPOutputStream(
new FileOutputStream(gzipFile))) {

byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
gzos.write(buffer, 0, len);
}

System.out.println("压缩完成: " + sourceFile + " -> " + gzipFile);
}
}

/**
* 压缩字符串为 GZIP 字节数组
*/
public static byte[] compressString(String text) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzos = new GZIPOutputStream(baos)) {

gzos.write(text.getBytes(StandardCharsets.UTF_8));
gzos.finish(); // 完成压缩

return baos.toByteArray();
}
}

public static void main(String[] args) {
try {
// 压缩文件
compressFile("data.txt", "data.txt.gz");

// 压缩字符串
String text = "Hello, GZIP Compression!";
byte[] compressed = compressString(text);
System.out.println("原始大小: " + text.length() + " bytes");
System.out.println("压缩后大小: " + compressed.length + " bytes");
System.out.println("压缩率: " +
(1 - (double)compressed.length / text.length()) * 100 + "%");

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 高级配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import java.io.*;
import java.util.zip.*;

public class AdvancedGZIPOutputStream {

/**
* 配置 GZIP 压缩参数
*/
public static void configureGZIP(String sourceFile, String gzipFile)
throws IOException {

try (FileInputStream fis = new FileInputStream(sourceFile);
GZIPOutputStream gzos = new GZIPOutputStream(
new BufferedOutputStream(new FileOutputStream(gzipFile))) {
{
// 设置压缩缓冲区大小默认 512 字节
def.setLevel(Deflater.BEST_COMPRESSION); // 最高压缩比
def.setStrategy(Deflater.DEFAULT_STRATEGY);
}
}) {

byte[] buffer = new byte[65536]; // 64KB 缓冲区
int len;
while ((len = fis.read(buffer)) != -1) {
gzos.write(buffer, 0, len);
}

System.out.println("使用最高压缩比完成压缩");
}
}

/**
* 设置文件名和修改时间GZIP 头信息
*/
public static void setGZIPMetadata(String sourceFile, String gzipFile)
throws IOException {

try (FileInputStream fis = new FileInputStream(sourceFile);
GZIPOutputStream gzos = new GZIPOutputStream(
new FileOutputStream(gzipFile)) {
{
// 设置文件名可选
this.name = sourceFile;
// 设置修改时间可选
this.modTime = System.currentTimeMillis() / 1000;
}
}) {

byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
gzos.write(buffer, 0, len);
}
}
}

/**
* 使用不同的压缩级别
*/
public static void compressWithLevel(String sourceFile, String gzipFile, int level)
throws IOException {

try (FileInputStream fis = new FileInputStream(sourceFile);
GZIPOutputStream gzos = new GZIPOutputStream(
new FileOutputStream(gzipFile)) {
{
def.setLevel(level);
}
}) {

byte[] buffer = new byte[8192];
int len;
long startTime = System.currentTimeMillis();

while ((len = fis.read(buffer)) != -1) {
gzos.write(buffer, 0, len);
}

long endTime = System.currentTimeMillis();
File compressedFile = new File(gzipFile);

System.out.println("压缩级别: " + level);
System.out.println("压缩后大小: " + compressedFile.length() + " bytes");
System.out.println("压缩时间: " + (endTime - startTime) + " ms");
}
}

public static void main(String[] args) {
try {
// 测试不同压缩级别
String testFile = "large_file.txt";

System.out.println("=== 压缩级别对比 ===");
compressWithLevel(testFile, "level_0.gz", Deflater.NO_COMPRESSION);
compressWithLevel(testFile, "level_1.gz", Deflater.BEST_SPEED);
compressWithLevel(testFile, "level_5.gz", 5);
compressWithLevel(testFile, "level_9.gz", Deflater.BEST_COMPRESSION);

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 解压文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import java.io.*;
import java.util.zip.*;

public class GZIPInputStreamBasic {

/**
* 解压 GZIP 文件
*/
public static void decompressFile(String gzipFile, String outputFile)
throws IOException {

try (GZIPInputStream gzis = new GZIPInputStream(
new FileInputStream(gzipFile));
FileOutputStream fos = new FileOutputStream(outputFile)) {

byte[] buffer = new byte[8192];
int len;
while ((len = gzis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}

System.out.println("解压完成: " + gzipFile + " -> " + outputFile);
}
}

/**
* 解压 GZIP 字节数组为字符串
*/
public static String decompressString(byte[] compressed) throws IOException {
try (ByteArrayInputStream bais = new ByteArrayInputStream(compressed);
GZIPInputStream gzis = new GZIPInputStream(bais);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

byte[] buffer = new byte[8192];
int len;
while ((len = gzis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}

return baos.toString(StandardCharsets.UTF_8);
}
}

/**
* 逐行读取 GZIP 中的文本文件
*/
public static void readGZIPLineByLine(String gzipFile) throws IOException {
try (GZIPInputStream gzis = new GZIPInputStream(
new FileInputStream(gzipFile));
BufferedReader reader = new BufferedReader(
new InputStreamReader(gzis, StandardCharsets.UTF_8))) {

String line;
int lineNum = 0;
while ((line = reader.readLine()) != null) {
System.out.printf("%d: %s%n", ++lineNum, line);
}
}
}

public static void main(String[] args) {
try {
// 解压文件
decompressFile("data.txt.gz", "data_decompressed.txt");

// 解压字符串
byte[] compressed = compressString("Hello GZIP!");
String decompressed = decompressString(compressed);
System.out.println("解压后的字符串: " + decompressed);

// 逐行读取
readGZIPLineByLine("log.txt.gz");

} catch (IOException e) {
e.printStackTrace();
}
}

private static byte[] compressString(String text) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzos = new GZIPOutputStream(baos)) {
gzos.write(text.getBytes(StandardCharsets.UTF_8));
return baos.toByteArray();
}
}
}

⭐⭐ 高级读取功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import java.io.*;
import java.util.zip.*;

public class AdvancedGZIPInputStream {

/**
* 获取 GZIP 头信息
*/
public static void readGZIPHeader(String gzipFile) throws IOException {
try (GZIPInputStream gzis = new GZIPInputStream(
new FileInputStream(gzipFile))) {

// 通过反射获取头信息GZIPInputStream 没有直接提供 API
// 注意这依赖于实现细节不推荐在生产代码中使用

java.lang.reflect.Field field = GZIPInputStream.class
.getDeclaredField("crc");
field.setAccessible(true);
CRC32 crc = (CRC32) field.get(gzis);

System.out.println("CRC32: " + crc.getValue());

// 读取实际内容
byte[] buffer = new byte[8192];
while (gzis.read(buffer) != -1) {
// 读取内容
}
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 带进度显示的解压
*/
public static void decompressWithProgress(String gzipFile, String outputFile)
throws IOException {

File compressed = new File(gzipFile);
long totalSize = compressed.length();
long processed = 0;
int lastProgress = 0;

try (GZIPInputStream gzis = new GZIPInputStream(
new FileInputStream(gzipFile));
FileOutputStream fos = new FileOutputStream(outputFile)) {

byte[] buffer = new byte[8192];
int len;

while ((len = gzis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
processed += len;

int progress = (int) (processed * 100 / totalSize);
if (progress > lastProgress) {
System.out.printf("\r解压进度: %d%%", progress);
lastProgress = progress;
}
}
System.out.println("\n解压完成!");
}
}

/**
* 解压到内存适合小文件
*/
public static byte[] decompressToBytes(String gzipFile) throws IOException {
try (GZIPInputStream gzis = new GZIPInputStream(
new FileInputStream(gzipFile));
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

byte[] buffer = new byte[8192];
int len;
while ((len = gzis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}

return baos.toByteArray();
}
}

public static void main(String[] args) {
try {
// 带进度的解压
decompressWithProgress("large_file.gz", "large_file.txt");

// 读取到内存
byte[] content = decompressToBytes("config.gz");
System.out.println("文件大小: " + content.length + " bytes");

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 处理 TAR.GZ 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import java.io.*;
import java.util.zip.*;
import java.nio.file.*;
import java.util.*;

public class TarGZHandler {

/**
* 创建 tar.gz 文件
*/
public static void createTarGZ(String sourceDir, String tarGzFile)
throws IOException {

try (FileOutputStream fos = new FileOutputStream(tarGzFile);
GZIPOutputStream gzos = new GZIPOutputStream(fos);
TarOutputStream tos = new TarOutputStream(gzos)) {

Path sourcePath = Paths.get(sourceDir);
Files.walk(sourcePath)
.filter(path -> !Files.isDirectory(path))
.forEach(path -> {
try {
String entryName = sourcePath.relativize(path).toString()
.replace("\\", "/");

TarEntry entry = new TarEntry(path.toFile(), entryName);
tos.putNextEntry(entry);

Files.copy(path, tos);
tos.closeEntry();

System.out.println("添加: " + entryName);
} catch (IOException e) {
e.printStackTrace();
}
});

System.out.println("创建 tar.gz 完成: " + tarGzFile);
}
}

/**
* 解压 tar.gz 文件
*/
public static void extractTarGZ(String tarGzFile, String outputDir)
throws IOException {

try (FileInputStream fis = new FileInputStream(tarGzFile);
GZIPInputStream gzis = new GZIPInputStream(fis);
TarInputStream tis = new TarInputStream(gzis)) {

TarEntry entry;
while ((entry = tis.getNextEntry()) != null) {
File outputFile = new File(outputDir, entry.getName());

if (entry.isDirectory()) {
outputFile.mkdirs();
} else {
// 确保父目录存在
outputFile.getParentFile().mkdirs();

try (FileOutputStream fos = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[8192];
int len;
while ((len = tis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}
}

System.out.println("提取: " + entry.getName());
}
}
}

public static void main(String[] args) {
try {
// 创建 tar.gz
createTarGZ("docs", "docs.tar.gz");

// 解压 tar.gz
extractTarGZ("docs.tar.gz", "extracted");

} catch (IOException e) {
e.printStackTrace();
}
}
}

// TarEntry 和 TarOutputStream 不是 Java 标准库的一部分需要添加 Apache Commons Compress 依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.25.0</version>
</dependency>

⭐⭐ 缓冲区优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class GZIPPerformance {

/**
* 使用大缓冲区提高性能
*/
public static void optimizeBuffer(String sourceFile, String gzipFile)
throws IOException {

// 使用 64KB 缓冲区
int bufferSize = 65536;

try (FileInputStream fis = new FileInputStream(sourceFile);
BufferedInputStream bis = new BufferedInputStream(fis, bufferSize);
FileOutputStream fos = new FileOutputStream(gzipFile);
GZIPOutputStream gzos = new GZIPOutputStream(
new BufferedOutputStream(fos, bufferSize))) {

byte[] buffer = new byte[bufferSize];
int len;
long startTime = System.currentTimeMillis();

while ((len = bis.read(buffer)) != -1) {
gzos.write(buffer, 0, len);
}

long endTime = System.currentTimeMillis();
System.out.println("压缩时间: " + (endTime - startTime) + " ms");
}
}

/**
* 并行压缩多个文件使用线程池
*/
public static void parallelCompress(List<String> files, String outputDir)
throws InterruptedException {

ExecutorService executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());

List<Future<?>> futures = new ArrayList<>();

for (String file : files) {
futures.add(executor.submit(() -> {
try {
String gzipFile = outputDir + File.separator +
new File(file).getName() + ".gz";
compressFile(file, gzipFile);
} catch (IOException e) {
e.printStackTrace();
}
}));
}

// 等待所有任务完成
for (Future<?> future : futures) {
try {
future.get();
} catch (ExecutionException e) {
e.printStackTrace();
}
}

executor.shutdown();
System.out.println("并行压缩完成");
}

private static void compressFile(String source, String target) throws IOException {
try (FileInputStream fis = new FileInputStream(source);
GZIPOutputStream gzos = new GZIPOutputStream(
new FileOutputStream(target))) {

byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
gzos.write(buffer, 0, len);
}
}
}
}

⭐⭐ 内存映射文件优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;

public class MappedGZIP {

/**
* 使用内存映射文件处理大文件
*/
public static void compressWithMappedFile(String sourceFile, String gzipFile)
throws IOException {

Path path = Paths.get(sourceFile);
long fileSize = Files.size(path);

try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);
FileOutputStream fos = new FileOutputStream(gzipFile);
GZIPOutputStream gzos = new GZIPOutputStream(fos)) {

// 映射整个文件到内存
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY, 0, fileSize);

byte[] bytes = new byte[8192];
long startTime = System.currentTimeMillis();

while (buffer.hasRemaining()) {
int remaining = buffer.remaining();
int readSize = Math.min(bytes.length, remaining);
buffer.get(bytes, 0, readSize);
gzos.write(bytes, 0, readSize);
}

long endTime = System.currentTimeMillis();
System.out.println("内存映射压缩时间: " + (endTime - startTime) + " ms");
}
}

/**
* 使用内存映射解压
*/
public static void decompressWithMappedFile(String gzipFile, String outputFile)
throws IOException {

try (FileInputStream fis = new FileInputStream(gzipFile);
GZIPInputStream gzis = new GZIPInputStream(fis);
RandomAccessFile raf = new RandomAccessFile(outputFile, "rw");
FileChannel channel = raf.getChannel()) {

// 先解压到临时缓冲区
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
int len;
while ((len = gzis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}

byte[] decompressed = baos.toByteArray();

// 映射输出文件
MappedByteBuffer outBuffer = channel.map(
FileChannel.MapMode.READ_WRITE, 0, decompressed.length);
outBuffer.put(decompressed);

System.out.println("内存映射解压完成大小: " + decompressed.length + " bytes");
}
}
}

⭐⭐ 处理大文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class LargeFileHandler {

/**
* 流式处理大文件避免内存溢出
*/
public static void processLargeGZIP(String gzipFile) throws IOException {
try (GZIPInputStream gzis = new GZIPInputStream(
new BufferedInputStream(new FileInputStream(gzipFile), 65536));
BufferedReader reader = new BufferedReader(
new InputStreamReader(gzis, StandardCharsets.UTF_8))) {

String line;
int lineCount = 0;

// 逐行处理不会一次性加载整个文件
while ((line = reader.readLine()) != null) {
// 处理每一行
processLine(line);
lineCount++;

// 定期输出进度
if (lineCount % 10000 == 0) {
System.out.println("已处理 " + lineCount + " 行");
}
}

System.out.println("共处理 " + lineCount + " 行");
}
}

private static void processLine(String line) {
// 处理逻辑
}
}

⭐⭐ 资源管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class GZIPResourceManagement {

/**
* 正确关闭流使用 try-with-resources
*/
public static void properClose(String source, String target) throws IOException {
// 自动关闭所有资源
try (FileInputStream fis = new FileInputStream(source);
GZIPOutputStream gzos = new GZIPOutputStream(
new FileOutputStream(target))) {

byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
gzos.write(buffer, 0, len);
}

// 注意不需要调用 finish()close() 会自动调用
}
}

/**
* 手动调用 finish() 确保完成压缩
*/
public static void ensureFinish(String source, String target) throws IOException {
GZIPOutputStream gzos = null;
try {
FileInputStream fis = new FileInputStream(source);
gzos = new GZIPOutputStream(new FileOutputStream(target));

byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
gzos.write(buffer, 0, len);
}

// 确保所有数据被写入
gzos.finish();

} finally {
if (gzos != null) {
gzos.close();
}
}
}
}

JAR 文件处理

JarInputStreamJarOutputStream它们是专门用于流式处理 JAR 文件的类继承自 ZipInputStreamZipOutputStream增加了对 JAR 清单Manifest和签名文件的支持

⭐⭐ 创建 JAR 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import java.io.*;
import java.util.jar.*;
import java.util.zip.*;

public class JarOutputStreamBasic {

/**
* 创建基本的 JAR 文件
*/
public static void createBasicJar(String jarPath, String[] files)
throws IOException {

try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(jarPath))) {

// 添加文件
for (String filePath : files) {
File file = new File(filePath);
if (!file.exists()) continue;

// 创建 JarEntry
JarEntry entry = new JarEntry(file.getName());
entry.setTime(file.lastModified());

// 添加到 JAR
jos.putNextEntry(entry);

// 写入文件内容
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}
}

jos.closeEntry();
System.out.println("添加: " + file.getName());
}

System.out.println("JAR 创建成功: " + jarPath);
}
}

/**
* 创建带清单的 JAR 文件
*/
public static void createJarWithManifest(String jarPath, String mainClass,
String[] classFiles) throws IOException {

// 创建清单
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attributes.put(Attributes.Name.MAIN_CLASS, mainClass);
attributes.put(Attributes.Name.CREATED_BY, "JarOutputStream Example");

try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(jarPath), manifest)) {

// 添加类文件
for (String classFile : classFiles) {
File file = new File(classFile);
String entryName = classFile.replace(File.separator, "/");

JarEntry entry = new JarEntry(entryName);
entry.setTime(file.lastModified());

jos.putNextEntry(entry);

try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}
}

jos.closeEntry();
System.out.println("添加类: " + entryName);
}

System.out.println("可执行 JAR 创建成功: " + jarPath);
System.out.println("Main-Class: " + mainClass);
}
}

/**
* 创建带目录结构的 JAR
*/
public static void createJarWithDirectories(String jarPath, String sourceDir)
throws IOException {

try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(jarPath))) {

Path sourcePath = Paths.get(sourceDir);

Files.walk(sourcePath)
.filter(path -> !Files.isDirectory(path))
.forEach(path -> {
try {
String entryName = sourcePath.relativize(path).toString()
.replace("\\", "/");

JarEntry entry = new JarEntry(entryName);
entry.setTime(Files.getLastModifiedTime(path).toMillis());

jos.putNextEntry(entry);
Files.copy(path, jos);
jos.closeEntry();

System.out.println("添加: " + entryName);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}

public static void main(String[] args) {
try {
// 基本 JAR
String[] files = {"README.txt", "config.properties"};
createBasicJar("simple.jar", files);

// 可执行 JAR
String[] classes = {"com/example/Main.class", "com/example/Utils.class"};
createJarWithManifest("app.jar", "com.example.Main", classes);

// 带目录结构的 JAR
createJarWithDirectories("structure.jar", "build/classes");

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 高级 JAR 创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
import java.io.*;
import java.util.jar.*;
import java.util.zip.*;
import java.util.*;
import java.nio.file.*;

public class AdvancedJarOutputStream {

/**
* 创建带版本信息的 JAR
*/
public static void createVersionedJar(String jarPath, String version,
String[] files) throws IOException {

Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attributes.put(new Attributes.Name("Implementation-Version"), version);
attributes.put(new Attributes.Name("Implementation-Title"), "My Application");
attributes.put(new Attributes.Name("Implementation-Vendor"), "My Company");
attributes.put(new Attributes.Name("Build-Date"), new Date().toString());

try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(jarPath), manifest)) {

for (String filePath : files) {
File file = new File(filePath);
JarEntry entry = new JarEntry(file.getName());
entry.setTime(file.lastModified());

jos.putNextEntry(entry);

try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}
}

jos.closeEntry();
}

System.out.println("版本化 JAR 创建成功: " + jarPath);
System.out.println("版本: " + version);
}
}

/**
* 创建带 Class-Path 的 JAR
*/
public static void createJarWithClassPath(String jarPath, String mainClass,
List<String> dependencies,
String[] classFiles) throws IOException {

Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attributes.put(Attributes.Name.MAIN_CLASS, mainClass);

// 设置 Class-Path
if (!dependencies.isEmpty()) {
StringBuilder classPath = new StringBuilder();
for (String dep : dependencies) {
if (classPath.length() > 0) classPath.append(" ");
classPath.append(dep);
}
attributes.put(Attributes.Name.CLASS_PATH, classPath.toString());
}

try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(jarPath), manifest)) {

// 添加类文件
for (String classFile : classFiles) {
File file = new File(classFile);
String entryName = classFile.replace(File.separator, "/");

JarEntry entry = new JarEntry(entryName);
jos.putNextEntry(entry);

try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}
}

jos.closeEntry();
}
}

System.out.println("带 Class-Path 的 JAR 创建成功");
System.out.println("Class-Path: " + dependencies);
}

/**
* 创建带自定义属性的 JAR
*/
public static void createJarWithCustomAttributes(String jarPath,
Map<String, String> customAttrs,
Map<String, byte[]> entries)
throws IOException {

Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");

// 添加自定义属性
for (Map.Entry<String, String> attr : customAttrs.entrySet()) {
attributes.put(new Attributes.Name(attr.getKey()), attr.getValue());
}

// 添加条目属性
for (Map.Entry<String, byte[]> entry : entries.entrySet()) {
Attributes entryAttrs = new Attributes();
entryAttrs.put(new Attributes.Name("SHA-256-Digest"),
calculateSHA256(entry.getValue()));
manifest.getEntries().put(entry.getKey(), entryAttrs);
}

try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(jarPath), manifest)) {

for (Map.Entry<String, byte[]> entry : entries.entrySet()) {
JarEntry jarEntry = new JarEntry(entry.getKey());
jos.putNextEntry(jarEntry);
jos.write(entry.getValue());
jos.closeEntry();

System.out.println("添加: " + entry.getKey());
}
}
}

/**
* 创建带签名的 JAR示例框架
*/
public static void createSignedJar(String jarPath, String[] files)
throws IOException {

// 注意实际签名需要使用 jarsigner 工具或第三方库
// 这里只演示创建 JAR 并预留签名文件位置

Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");

try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(jarPath), manifest)) {

// 添加文件
for (String filePath : files) {
File file = new File(filePath);
JarEntry entry = new JarEntry(file.getName());
jos.putNextEntry(entry);

try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}
}

jos.closeEntry();
}

// 预留签名文件位置实际签名时需要添加 .SF 和 .DSA/.RSA 文件
System.out.println("JAR 创建完成可以使用 jarsigner 签名");
System.out.println("签名命令: jarsigner " + jarPath + " mykeystore");
}
}

private static String calculateSHA256(byte[] data) {
try {
java.security.MessageDigest md =
java.security.MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(data);
return java.util.Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
return "";
}
}

public static void main(String[] args) {
try {
// 创建版本化 JAR
String[] files = {"app.properties", "logo.png"};
createVersionedJar("versioned.jar", "2.0.1", files);

// 创建带依赖的 JAR
List<String> deps = Arrays.asList("lib/common.jar", "lib/utils.jar");
String[] classes = {"com/app/Main.class"};
createJarWithClassPath("app-with-deps.jar", "com.app.Main", deps, classes);

// 创建带自定义属性的 JAR
Map<String, String> attrs = new HashMap<>();
attrs.put("Built-By", "Developer");
attrs.put("Build-Time", new Date().toString());

Map<String, byte[]> entries = new HashMap<>();
entries.put("data.txt", "Hello World".getBytes());
entries.put("config/settings.xml", "<config>...</config>".getBytes());

createJarWithCustomAttributes("custom.jar", attrs, entries);

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 读取 JAR 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import java.io.*;
import java.util.jar.*;
import java.util.zip.*;

public class JarInputStreamBasic {

/**
* 读取 JAR 文件内容
*/
public static void readJarFile(String jarPath) throws IOException {
try (JarInputStream jis = new JarInputStream(
new FileInputStream(jarPath))) {

// 读取清单
Manifest manifest = jis.getManifest();
if (manifest != null) {
System.out.println("=== 清单内容 ===");
Attributes attributes = manifest.getMainAttributes();
for (Object key : attributes.keySet()) {
System.out.printf("%s: %s%n", key, attributes.get(key));
}
}

// 读取条目
System.out.println("\n=== JAR 内容 ===");
JarEntry entry;
int fileCount = 0;

while ((entry = jis.getNextJarEntry()) != null) {
if (entry.isDirectory()) {
System.out.printf("[目录] %s%n", entry.getName());
} else {
System.out.printf("[文件] %s (%,d bytes)%n",
entry.getName(), entry.getSize());

// 读取内容如果需要
if (entry.getName().endsWith(".properties")) {
System.out.println(" 内容预览:");
byte[] buffer = new byte[200];
int len = jis.read(buffer);
if (len > 0) {
System.out.println(" " +
new String(buffer, 0, Math.min(len, 100)));
}
}

fileCount++;
}
jis.closeEntry();
}

System.out.println("\n总计: " + fileCount + " 个文件");
}
}

/**
* 解压 JAR 文件
*/
public static void extractJar(String jarPath, String outputDir)
throws IOException {

try (JarInputStream jis = new JarInputStream(
new FileInputStream(jarPath))) {

JarEntry entry;
while ((entry = jis.getNextJarEntry()) != null) {
File outputFile = new File(outputDir, entry.getName());

if (entry.isDirectory()) {
outputFile.mkdirs();
System.out.println("创建目录: " + entry.getName());
} else {
// 确保父目录存在
outputFile.getParentFile().mkdirs();

try (FileOutputStream fos = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[8192];
int len;
while ((len = jis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}

System.out.println("提取文件: " + entry.getName());
}

jis.closeEntry();
}
}

System.out.println("解压完成: " + outputDir);
}

/**
* 只读取清单文件
*/
public static void readManifestOnly(String jarPath) throws IOException {
try (JarInputStream jis = new JarInputStream(
new FileInputStream(jarPath))) {

Manifest manifest = jis.getManifest();
if (manifest != null) {
System.out.println("=== JAR 清单信息 ===");

// 主属性
Attributes mainAttrs = manifest.getMainAttributes();
System.out.println("\n主属性:");
for (Object key : mainAttrs.keySet()) {
System.out.printf(" %s: %s%n", key, mainAttrs.get(key));
}

// 条目属性
Map<String, Attributes> entries = manifest.getEntries();
if (!entries.isEmpty()) {
System.out.println("\n条目属性:");
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
System.out.println(" " + entry.getKey() + ":");
for (Object key : entry.getValue().keySet()) {
System.out.printf(" %s: %s%n",
key, entry.getValue().get(key));
}
}
}
} else {
System.out.println("JAR 文件不包含清单");
}
}
}

public static void main(String[] args) {
try {
String jarPath = "app.jar";

// 读取 JAR 内容
readJarFile(jarPath);

// 解压 JAR
extractJar(jarPath, "extracted");

// 只读取清单
readManifestOnly(jarPath);

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 高级 JAR 读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
import java.io.*;
import java.util.jar.*;
import java.util.*;
import java.security.*;
import java.security.cert.*;

public class AdvancedJarInputStream {

/**
* 验证 JAR 签名
*/
public static void verifyJarSignature(String jarPath) throws IOException {
try (JarInputStream jis = new JarInputStream(
new FileInputStream(jarPath), true)) { // verify = true

System.out.println("=== 签名验证 ===");

Manifest manifest = jis.getManifest();
JarEntry entry;
boolean hasSignature = false;

while ((entry = jis.getNextJarEntry()) != null) {
String name = entry.getName();

// 检查签名文件
if (name.startsWith("META-INF/") &&
(name.endsWith(".SF") || name.endsWith(".DSA") ||
name.endsWith(".RSA") || name.endsWith(".EC"))) {
hasSignature = true;
System.out.println("找到签名文件: " + name);
}

// 读取并验证条目如果 JAR 已签名会自动验证
if (!entry.isDirectory() && !name.startsWith("META-INF/")) {
try {
byte[] buffer = new byte[8192];
while (jis.read(buffer) != -1) {
// 读取过程中会自动验证签名
}
System.out.printf("✓ 验证通过: %s%n", name);
} catch (SecurityException e) {
System.err.printf("✗ 签名验证失败: %s - %s%n",
name, e.getMessage());
}
}

jis.closeEntry();
}

if (!hasSignature) {
System.out.println("JAR 文件未签名");
}
}
}

/**
* 获取证书信息
*/
public static void getCertificateInfo(String jarPath) throws IOException {
try (JarInputStream jis = new JarInputStream(
new FileInputStream(jarPath), true)) {

System.out.println("=== 证书信息 ===");

JarEntry entry;
while ((entry = jis.getNextJarEntry()) != null) {
if (!entry.isDirectory() && !entry.getName().startsWith("META-INF/")) {
// 读取条目内容以触发签名验证
byte[] buffer = new byte[8192];
while (jis.read(buffer) != -1) {
// 读取内容
}

// 获取证书链
java.security.cert.Certificate[] certs = entry.getCertificates();
if (certs != null && certs.length > 0) {
System.out.println("\n条目: " + entry.getName());
for (int i = 0; i < certs.length; i++) {
java.security.cert.Certificate cert = certs[i];
System.out.printf(" 证书 %d:%n", i + 1);
System.out.printf(" 类型: %s%n", cert.getType());

if (cert instanceof X509Certificate) {
X509Certificate x509 = (X509Certificate) cert;
System.out.printf(" 颁发者: %s%n", x509.getIssuerDN());
System.out.printf(" 主题: %s%n", x509.getSubjectDN());
System.out.printf(" 有效期: %s 至 %s%n",
x509.getNotBefore(), x509.getNotAfter());
}
}
}
}
jis.closeEntry();
}
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 搜索 JAR 中的类
*/
public static List<String> searchClasses(String jarPath, String className)
throws IOException {

List<String> results = new ArrayList<>();
String searchPattern = className.replace('.', '/');

try (JarInputStream jis = new JarInputStream(
new FileInputStream(jarPath))) {

JarEntry entry;
while ((entry = jis.getNextJarEntry()) != null) {
String name = entry.getName();

if (name.endsWith(".class") && name.contains(searchPattern)) {
String classFullName = name.replace('/', '.')
.replace(".class", "");
results.add(classFullName);
}

jis.closeEntry();
}
}

return results;
}

/**
* 提取 JAR 中的特定类型文件
*/
public static void extractFilesByType(String jarPath, String outputDir,
String extension) throws IOException {

try (JarInputStream jis = new JarInputStream(
new FileInputStream(jarPath))) {

JarEntry entry;
int count = 0;

while ((entry = jis.getNextJarEntry()) != null) {
String name = entry.getName();

if (!entry.isDirectory() && name.endsWith(extension)) {
File outputFile = new File(outputDir, name);
outputFile.getParentFile().mkdirs();

try (FileOutputStream fos = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[8192];
int len;
while ((len = jis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}

System.out.println("提取: " + name);
count++;
}

jis.closeEntry();
}

System.out.println("提取了 " + count + " 个 " + extension + " 文件");
}
}

/**
* 分析 JAR 依赖
*/
public static void analyzeDependencies(String jarPath) throws IOException {
System.out.println("=== 依赖分析 ===");

Set<String> packages = new TreeSet<>();

try (JarInputStream jis = new JarInputStream(
new FileInputStream(jarPath))) {

JarEntry entry;
while ((entry = jis.getNextJarEntry()) != null) {
String name = entry.getName();

if (name.endsWith(".class")) {
// 读取类文件以分析依赖
try {
byte[] buffer = new byte[8192];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len;
while ((len = jis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}

// 简单分析提取包名
String className = name.replace('/', '.').replace(".class", "");
int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
String pkg = className.substring(0, lastDot);
packages.add(pkg);
}

} catch (IOException e) {
// 忽略读取错误
}
}

jis.closeEntry();
}
}

System.out.println("包含的包:");
for (String pkg : packages) {
System.out.println(" " + pkg);
}
}

public static void main(String[] args) {
try {
String jarPath = "app.jar";

// 验证签名
verifyJarSignature(jarPath);

// 获取证书信息
getCertificateInfo(jarPath);

// 搜索类
List<String> classes = searchClasses(jarPath, "com.example");
System.out.println("\n找到的类:");
for (String clazz : classes) {
System.out.println(" " + clazz);
}

// 提取资源文件
extractFilesByType(jarPath, "resources", ".properties");

// 分析依赖
analyzeDependencies(jarPath);

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 流式处理大文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import java.io.*;
import java.util.jar.*;
import java.util.zip.*;

public class StreamJarProcessing {

/**
* 流式处理 JAR避免内存溢出
*/
public static void streamProcessJar(String jarPath, EntryProcessor processor)
throws IOException {

try (JarInputStream jis = new JarInputStream(
new BufferedInputStream(new FileInputStream(jarPath), 65536))) {

JarEntry entry;
byte[] buffer = new byte[65536];

while ((entry = jis.getNextJarEntry()) != null) {
if (!entry.isDirectory()) {
// 处理条目
processor.process(entry, jis);
}
jis.closeEntry();
}
}
}

@FunctionalInterface
public interface EntryProcessor {
void process(JarEntry entry, InputStream is) throws IOException;
}

/**
* 边读边写转换 JAR 内容
*/
public static void transformJar(String inputJar, String outputJar,
EntryTransformer transformer) throws IOException {

try (JarInputStream jis = new JarInputStream(
new FileInputStream(inputJar));
JarOutputStream jos = new JarOutputStream(
new FileOutputStream(outputJar))) {

// 复制清单
Manifest manifest = jis.getManifest();
if (manifest != null) {
// JarOutputStream 会自动处理清单这里不需要额外操作
}

JarEntry entry;
while ((entry = jis.getNextJarEntry()) != null) {
if (entry.isDirectory()) {
// 跳过目录
jis.closeEntry();
continue;
}

// 转换条目
JarEntry newEntry = transformer.transform(entry);
jos.putNextEntry(newEntry);

// 复制或转换内容
byte[] buffer = new byte[8192];
int len;
while ((len = jis.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}

jos.closeEntry();
jis.closeEntry();

System.out.println("处理: " + entry.getName());
}
}
}

@FunctionalInterface
public interface EntryTransformer {
JarEntry transform(JarEntry original);
}

/**
* 过滤 JAR 内容
*/
public static void filterJar(String inputJar, String outputJar,
EntryFilter filter) throws IOException {

try (JarInputStream jis = new JarInputStream(
new FileInputStream(inputJar));
JarOutputStream jos = new JarOutputStream(
new FileOutputStream(outputJar))) {

// 复制清单
Manifest manifest = jis.getManifest();
if (manifest != null) {
// 可以修改清单
}

JarEntry entry;
int kept = 0;
int skipped = 0;

while ((entry = jis.getNextJarEntry()) != null) {
if (filter.shouldKeep(entry)) {
// 保留条目
JarEntry newEntry = new JarEntry(entry.getName());
newEntry.setTime(entry.getTime());
jos.putNextEntry(newEntry);

byte[] buffer = new byte[8192];
int len;
while ((len = jis.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}

jos.closeEntry();
kept++;
} else {
skipped++;
}

jis.closeEntry();
}

System.out.printf("过滤完成: 保留 %d, 跳过 %d%n", kept, skipped);
}
}

@FunctionalInterface
public interface EntryFilter {
boolean shouldKeep(JarEntry entry);
}

/**
* 合并多个 JAR 文件
*/
public static void mergeJars(List<String> jarPaths, String outputJar)
throws IOException {

try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(outputJar))) {

Set<String> processedEntries = new HashSet<>();

for (String jarPath : jarPaths) {
System.out.println("处理: " + jarPath);

try (JarInputStream jis = new JarInputStream(
new FileInputStream(jarPath))) {

JarEntry entry;
while ((entry = jis.getNextJarEntry()) != null) {
String name = entry.getName();

// 跳过重复的条目保留第一个
if (!processedEntries.contains(name)) {
processedEntries.add(name);

JarEntry newEntry = new JarEntry(name);
newEntry.setTime(entry.getTime());
jos.putNextEntry(newEntry);

byte[] buffer = new byte[8192];
int len;
while ((len = jis.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}

jos.closeEntry();
}

jis.closeEntry();
}
}
}

System.out.println("合并完成共 " + processedEntries.size() + " 个条目");
}
}

public static void main(String[] args) {
try {
// 流式处理示例
streamProcessJar("large.jar", (entry, is) -> {
System.out.println("处理: " + entry.getName());
// 这里可以处理每个条目比如计算大小校验和等
byte[] buffer = new byte[8192];
long size = 0;
int len;
while ((len = is.read(buffer)) != -1) {
size += len;
}
System.out.println(" 大小: " + size + " bytes");
});

// 转换 JAR例如添加前缀
transformJar("input.jar", "output.jar", entry -> {
JarEntry newEntry = new JarEntry("prefixed/" + entry.getName());
newEntry.setTime(entry.getTime());
return newEntry;
});

// 过滤 JAR例如只保留 .class 文件
filterJar("app.jar", "filtered.jar", entry ->
entry.getName().endsWith(".class"));

// 合并 JAR
List<String> jars = Arrays.asList("lib1.jar", "lib2.jar", "lib3.jar");
mergeJars(jars, "merged.jar");

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
import java.io.*;
import java.util.jar.*;
import java.util.zip.*;
import java.util.concurrent.*;

public class JarStreamOptimization {

/**
* 使用缓冲流提高性能
*/
public static void createOptimizedJar(String jarPath, String sourceDir)
throws IOException {

try (JarOutputStream jos = new JarOutputStream(
new BufferedOutputStream(new FileOutputStream(jarPath), 65536))) {

Path sourcePath = Paths.get(sourceDir);

Files.walk(sourcePath)
.filter(path -> !Files.isDirectory(path))
.forEach(path -> {
try {
String entryName = sourcePath.relativize(path).toString()
.replace("\\", "/");

JarEntry entry = new JarEntry(entryName);
jos.putNextEntry(entry);

// 使用缓冲流复制
try (InputStream is = new BufferedInputStream(
Files.newInputStream(path), 65536)) {
byte[] buffer = new byte[65536];
int len;
while ((len = is.read(buffer)) != -1) {
jos.write(buffer, 0, len);
}
}

jos.closeEntry();
} catch (IOException e) {
e.printStackTrace();
}
});
}
}

/**
* 并行处理多个 JAR
*/
public static void parallelProcessJars(List<String> jarPaths,
JarProcessor processor)
throws InterruptedException {

ExecutorService executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());

List<Future<?>> futures = new ArrayList<>();

for (String jarPath : jarPaths) {
futures.add(executor.submit(() -> {
try {
processor.process(jarPath);
} catch (IOException e) {
e.printStackTrace();
}
}));
}

// 等待所有任务完成
for (Future<?> future : futures) {
try {
future.get();
} catch (ExecutionException e) {
e.printStackTrace();
}
}

executor.shutdown();
System.out.println("并行处理完成");
}

@FunctionalInterface
public interface JarProcessor {
void process(String jarPath) throws IOException;
}

/**
* 缓存 JAR 条目索引
*/
public static class JarIndexCache {
private final Map<String, Map<String, JarEntryInfo>> cache = new ConcurrentHashMap<>();

public void indexJar(String jarPath) throws IOException {
Map<String, JarEntryInfo> index = new HashMap<>();

try (JarInputStream jis = new JarInputStream(
new FileInputStream(jarPath))) {

JarEntry entry;
while ((entry = jis.getNextJarEntry()) != null) {
if (!entry.isDirectory()) {
// 计算条目位置近似
long position = getEntryPosition(jis);
index.put(entry.getName(),
new JarEntryInfo(entry, position));
}
jis.closeEntry();
}
}

cache.put(jarPath, index);
System.out.println("索引构建完成: " + jarPath +
" (" + index.size() + " 个条目)");
}

public JarEntryInfo getEntryInfo(String jarPath, String entryName) {
Map<String, JarEntryInfo> index = cache.get(jarPath);
return index != null ? index.get(entryName) : null;
}

private long getEntryPosition(JarInputStream jis) {
// 这是一个简化实现实际需要更复杂的位置计算
try {
java.lang.reflect.Field field = ZipInputStream.class
.getDeclaredField("pos");
field.setAccessible(true);
return field.getLong(jis);
} catch (Exception e) {
return -1;
}
}

static class JarEntryInfo {
final JarEntry entry;
final long position;

JarEntryInfo(JarEntry entry, long position) {
this.entry = entry;
this.position = position;
}

public JarEntry getEntry() { return entry; }
public long getPosition() { return position; }
}
}

/**
* 比较两种读取方式的性能
*/
public static void comparePerformance(String jarPath) throws IOException {
// 方式1: 使用 JarInputStream
long start1 = System.currentTimeMillis();
try (JarInputStream jis = new JarInputStream(
new FileInputStream(jarPath))) {

JarEntry entry;
while ((entry = jis.getNextJarEntry()) != null) {
if (!entry.isDirectory()) {
byte[] buffer = new byte[8192];
while (jis.read(buffer) != -1) {
// 读取内容
}
}
jis.closeEntry();
}
}
long time1 = System.currentTimeMillis() - start1;

// 方式2: 使用 JarFile随机访问
long start2 = System.currentTimeMillis();
try (JarFile jarFile = new JarFile(jarPath)) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (!entry.isDirectory()) {
try (InputStream is = jarFile.getInputStream(entry)) {
byte[] buffer = new byte[8192];
while (is.read(buffer) != -1) {
// 读取内容
}
}
}
}
}
long time2 = System.currentTimeMillis() - start2;

System.out.println("JarInputStream 耗时: " + time1 + " ms");
System.out.println("JarFile 耗时: " + time2 + " ms");
System.out.println("性能差异: " +
(time1 > time2 ? "JarFile 快 " + (time1 - time2) :
"JarInputStream 快 " + (time2 - time1)) + " ms");
}

public static void main(String[] args) {
try {
// 优化创建
createOptimizedJar("optimized.jar", "build/classes");

// 并行处理
List<String> jars = Arrays.asList("jar1.jar", "jar2.jar", "jar3.jar");
parallelProcessJars(jars, jarPath -> {
System.out.println("处理: " + jarPath);
// 处理 JAR
});

// 比较性能
comparePerformance("large.jar");

} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}

⭐⭐

1

计算校验和

CheckedInputStreamCheckedOutputStream 是 Java 中用于在数据流传输过程中计算校验和的类它们通常与其他流配合使用确保数据的完整性

  • 检测数据传输或存储过程中的错误
  • 验证数据完整性
  • 常用于文件压缩如 ZIP 文件的 CRC32 校验

⭐⭐ Java 提供了两种校验和实现

1
2
3
4
5
// CRC32 - 32位循环冗余校验
Checksum crc32 = new CRC32();

// Adler32 - 更快的校验和算法比 CRC32 快但可靠性稍低
Checksum adler32 = new Adler32();

⭐⭐ 写入文件并计算校验和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import java.io.*;
import java.util.zip.*;

public class CheckedOutputStreamBasic {

/**
* 写入文件并同时计算校验和
*/
public static void writeWithChecksum(String filePath, byte[] data)
throws IOException {

// 创建校验和对象
Checksum checksum = new CRC32();

try (CheckedOutputStream cos = new CheckedOutputStream(
new FileOutputStream(filePath), checksum)) {

// 写入数据
cos.write(data);

// 获取校验和
long checksumValue = cos.getChecksum().getValue();
System.out.println("CRC32 校验和: " + checksumValue);
System.out.println("十六进制: " + Long.toHexString(checksumValue));
}
}

/**
* 使用 Adler32 校验和
*/
public static void writeWithAdler32(String filePath, byte[] data)
throws IOException {

Checksum checksum = new Adler32();

try (CheckedOutputStream cos = new CheckedOutputStream(
new FileOutputStream(filePath), checksum)) {

cos.write(data);

long checksumValue = cos.getChecksum().getValue();
System.out.println("Adler32 校验和: " + checksumValue);
}
}

/**
* 逐步写入并更新校验和
*/
public static void writeInChunks(String filePath, String[] chunks)
throws IOException {

Checksum checksum = new CRC32();

try (CheckedOutputStream cos = new CheckedOutputStream(
new BufferedOutputStream(new FileOutputStream(filePath)),
checksum)) {

for (String chunk : chunks) {
byte[] data = chunk.getBytes();
cos.write(data);
System.out.printf("写入块: %s (当前校验和: %d)%n",
chunk, checksum.getValue());
}

System.out.println("最终校验和: " + checksum.getValue());
}
}

public static void main(String[] args) {
try {
String text = "Hello, CheckedOutputStream!";
writeWithChecksum("test.txt", text.getBytes());

writeWithAdler32("test2.txt", text.getBytes());

String[] chunks = {"Hello", " ", "World", "!"};
writeInChunks("chunks.txt", chunks);

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 批量创建校验和文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import java.io.*;
import java.util.zip.*;

public class ZipWithChecksum {

/**
* 创建带校验和的 ZIP 条目
*/
public static void createZipWithChecksum(String zipPath, String filePath)
throws IOException {

try (ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream(zipPath))) {

File file = new File(filePath);
ZipEntry entry = new ZipEntry(file.getName());

// 使用 CheckedOutputStream 计算校验和
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Checksum checksum = new CRC32();

try (CheckedOutputStream cos = new CheckedOutputStream(baos, checksum);
FileInputStream fis = new FileInputStream(file)) {

byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
cos.write(buffer, 0, len);
}
}

// 设置 ZIP 条目的属性
entry.setMethod(ZipEntry.STORED);
entry.setSize(file.length());
entry.setCompressedSize(file.length());
entry.setCrc(checksum.getValue()); // 设置 CRC32 校验和

// 写入 ZIP
zos.putNextEntry(entry);
zos.write(baos.toByteArray());
zos.closeEntry();

System.out.println("ZIP 创建成功CRC32: " + checksum.getValue());
}
}

/**
* 批量创建带校验和的 ZIP 文件
*/
public static void createMultiFileZip(String zipPath, List<String> filePaths)
throws IOException {

try (ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream(zipPath))) {

for (String filePath : filePaths) {
File file = new File(filePath);
if (!file.exists()) continue;

// 为每个文件计算校验和
Checksum checksum = new CRC32();
ByteArrayOutputStream baos = new ByteArrayOutputStream();

try (CheckedOutputStream cos = new CheckedOutputStream(baos, checksum);
FileInputStream fis = new FileInputStream(file)) {

byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
cos.write(buffer, 0, len);
}
}

// 创建 ZIP 条目
ZipEntry entry = new ZipEntry(file.getName());
entry.setMethod(ZipEntry.STORED);
entry.setSize(file.length());
entry.setCompressedSize(file.length());
entry.setCrc(checksum.getValue());

zos.putNextEntry(entry);
zos.write(baos.toByteArray());
zos.closeEntry();

System.out.printf("添加文件: %s (CRC: %d)%n",
file.getName(), checksum.getValue());
}
}
}

public static void main(String[] args) {
try {
createZipWithChecksum("output.zip", "data.txt");

List<String> files = Arrays.asList("file1.txt", "file2.txt", "image.jpg");
createMultiFileZip("multi.zip", files);

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 读取文件并验证校验和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import java.io.*;
import java.util.zip.*;

public class CheckedInputStreamBasic {

/**
* 读取文件并验证校验和
*/
public static void readWithChecksum(String filePath, long expectedChecksum)
throws IOException {

Checksum checksum = new CRC32();

try (CheckedInputStream cis = new CheckedInputStream(
new FileInputStream(filePath), checksum)) {

byte[] buffer = new byte[8192];
int len;
long totalBytes = 0;

// 读取数据
while ((len = cis.read(buffer)) != -1) {
totalBytes += len;
// 处理数据...
}

// 获取计算出的校验和
long actualChecksum = cis.getChecksum().getValue();

System.out.println("读取字节数: " + totalBytes);
System.out.println("实际校验和: " + actualChecksum);
System.out.println("期望校验和: " + expectedChecksum);

// 验证校验和
if (actualChecksum == expectedChecksum) {
System.out.println("✓ 校验和验证通过");
} else {
System.err.println("✗ 校验和验证失败文件可能已损坏");
}
}
}

/**
* 逐步读取并验证
*/
public static void readInChunks(String filePath) throws IOException {
Checksum checksum = new Adler32(); // 使用 Adler32 更快

try (CheckedInputStream cis = new CheckedInputStream(
new BufferedInputStream(new FileInputStream(filePath)),
checksum)) {

byte[] buffer = new byte[1024];
int len;
int chunkNum = 0;

while ((len = cis.read(buffer)) != -1) {
chunkNum++;
System.out.printf("读取块 %d: %d 字节 (当前校验和: %d)%n",
chunkNum, len, checksum.getValue());
}

System.out.println("最终校验和: " + checksum.getValue());
}
}

public static void main(String[] args) {
try {
// 先计算校验和
Checksum checksum = new CRC32();
byte[] data = "Test data".getBytes();
checksum.update(data, 0, data.length);
long expected = checksum.getValue();

// 创建测试文件
try (FileOutputStream fos = new FileOutputStream("test.txt")) {
fos.write(data);
}

// 读取并验证
readWithChecksum("test.txt", expected);

// 分块读取
readInChunks("test.txt");

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 验证 ZIP 文件完整性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import java.io.*;
import java.util.zip.*;

public class ZipIntegrityChecker {

/**
* 验证 ZIP 文件中所有条目的完整性
*/
public static void verifyZipIntegrity(String zipPath) throws IOException {

try (ZipFile zipFile = new ZipFile(zipPath)) {

Enumeration<? extends ZipEntry> entries = zipFile.entries();
boolean allValid = true;

while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();

if (!entry.isDirectory() && entry.getCrc() != -1) {
long storedCrc = entry.getCrc();

// 重新计算 CRC
Checksum checksum = new CRC32();

try (InputStream is = zipFile.getInputStream(entry);
CheckedInputStream cis = new CheckedInputStream(is, checksum)) {

byte[] buffer = new byte[8192];
while (cis.read(buffer) != -1) {
// 读取过程中会自动更新校验和
}

long calculatedCrc = checksum.getValue();

if (storedCrc == calculatedCrc) {
System.out.printf("✓ %s (CRC: %d)%n",
entry.getName(), calculatedCrc);
} else {
System.err.printf("✗ %s 损坏存储: %d, 计算: %d%n",
entry.getName(), storedCrc, calculatedCrc);
allValid = false;
}
}
}
}

if (allValid) {
System.out.println("所有文件验证通过");
} else {
System.err.println("ZIP 文件包含损坏的条目");
}
}
}

/**
* 带校验和验证的 ZIP 解压
*/
public static void extractWithVerification(String zipPath, String outputDir)
throws IOException {

try (ZipFile zipFile = new ZipFile(zipPath)) {

Enumeration<? extends ZipEntry> entries = zipFile.entries();
int successCount = 0;
int failCount = 0;

while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
File outputFile = new File(outputDir, entry.getName());

if (entry.isDirectory()) {
outputFile.mkdirs();
continue;
}

// 确保父目录存在
outputFile.getParentFile().mkdirs();

// 读取并验证
try (InputStream is = zipFile.getInputStream(entry);
CheckedInputStream cis = new CheckedInputStream(is, new CRC32());
FileOutputStream fos = new FileOutputStream(outputFile)) {

byte[] buffer = new byte[8192];
int len;
while ((len = cis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}

long calculatedCrc = cis.getChecksum().getValue();

if (entry.getCrc() == -1 || entry.getCrc() == calculatedCrc) {
System.out.printf("✓ 提取成功: %s%n", entry.getName());
successCount++;
} else {
System.err.printf("✗ CRC 不匹配: %s%n", entry.getName());
failCount++;
outputFile.delete(); // 删除损坏的文件
}
}
}

System.out.printf("解压完成: 成功 %d, 失败 %d%n", successCount, failCount);
}
}

public static void main(String[] args) {
try {
verifyZipIntegrity("archive.zip");
extractWithVerification("archive.zip", "extracted");
} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 网络传输完整性校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import java.io.*;
import java.net.*;
import java.util.zip.*;

public class NetworkTransferWithChecksum {

/**
* 发送带校验和的数据
*/
public static void sendWithChecksum(Socket socket, byte[] data)
throws IOException {

try (OutputStream os = socket.getOutputStream();
CheckedOutputStream cos = new CheckedOutputStream(os, new CRC32());
DataOutputStream dos = new DataOutputStream(cos)) {

// 发送数据长度
dos.writeInt(data.length);

// 发送数据
dos.write(data);

// 发送校验和
long checksum = cos.getChecksum().getValue();
dos.writeLong(checksum);

System.out.println("数据已发送长度: " + data.length +
", 校验和: " + checksum);
}
}

/**
* 接收并验证数据
*/
public static byte[] receiveWithVerification(Socket socket)
throws IOException {

try (InputStream is = socket.getInputStream();
CheckedInputStream cis = new CheckedInputStream(is, new CRC32());
DataInputStream dis = new DataInputStream(cis)) {

// 读取数据长度
int dataLength = dis.readInt();

// 读取数据
byte[] data = new byte[dataLength];
dis.readFully(data);

// 读取发送端校验和
long sentChecksum = dis.readLong();
long calculatedChecksum = cis.getChecksum().getValue();

if (sentChecksum == calculatedChecksum) {
System.out.println("✓ 数据验证通过");
return data;
} else {
throw new IOException("数据损坏发送: " + sentChecksum +
", 计算: " + calculatedChecksum);
}
}
}

/**
* 文件传输示例
*/
public static void sendFile(Socket socket, File file) throws IOException {
try (FileInputStream fis = new FileInputStream(file);
CheckedInputStream cis = new CheckedInputStream(fis, new CRC32());
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os)) {

// 发送文件名
dos.writeUTF(file.getName());

// 发送文件大小
long fileSize = file.length();
dos.writeLong(fileSize);

// 发送文件内容并计算校验和
byte[] buffer = new byte[8192];
int len;
while ((len = cis.read(buffer)) != -1) {
dos.write(buffer, 0, len);
}

// 发送校验和
long checksum = cis.getChecksum().getValue();
dos.writeLong(checksum);

System.out.println("文件发送完成: " + file.getName() +
" (校验和: " + checksum + ")");
}
}

public static void main(String[] args) {
// 服务器端示例
new Thread(() -> {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
Socket socket = serverSocket.accept();
byte[] data = receiveWithVerification(socket);
System.out.println("接收到的数据: " + new String(data));
} catch (IOException e) {
e.printStackTrace();
}
}).start();

// 客户端示例
try {
Thread.sleep(1000); // 等待服务器启动
Socket socket = new Socket("localhost", 8888);
sendWithChecksum(socket, "Hello, Network!".getBytes());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}

⭐⭐ 文件复制并验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import java.io.*;
import java.util.zip.*;

public class FileCopyWithChecksum {

/**
* 复制文件并计算校验和
*/
public static void copyFileWithChecksum(String source, String target)
throws IOException {

Checksum sourceChecksum = new CRC32();
Checksum targetChecksum = new CRC32();

try (FileInputStream fis = new FileInputStream(source);
CheckedInputStream cis = new CheckedInputStream(fis, sourceChecksum);
FileOutputStream fos = new FileOutputStream(target);
CheckedOutputStream cos = new CheckedOutputStream(fos, targetChecksum)) {

byte[] buffer = new byte[8192];
int len;
while ((len = cis.read(buffer)) != -1) {
cos.write(buffer, 0, len);
}
}

long sourceCrc = sourceChecksum.getValue();
long targetCrc = targetChecksum.getValue();

System.out.println("源文件 CRC: " + sourceCrc);
System.out.println("目标文件 CRC: " + targetCrc);

if (sourceCrc == targetCrc) {
System.out.println("✓ 文件复制成功完整性验证通过");
} else {
System.err.println("✗ 文件复制可能出错CRC 不匹配");
}
}

/**
* 备份文件并生成校验和文件
*/
public static void backupWithChecksum(String source, String backupDir)
throws IOException {

File sourceFile = new File(source);
File backupFile = new File(backupDir, sourceFile.getName() + ".bak");
File checksumFile = new File(backupDir, sourceFile.getName() + ".crc");

Checksum checksum = new CRC32();

// 复制文件并计算校验和
try (FileInputStream fis = new FileInputStream(sourceFile);
CheckedInputStream cis = new CheckedInputStream(fis, checksum);
FileOutputStream fos = new FileOutputStream(backupFile)) {

byte[] buffer = new byte[8192];
int len;
while ((len = cis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}

// 保存校验和
try (DataOutputStream dos = new DataOutputStream(
new FileOutputStream(checksumFile))) {
dos.writeLong(checksum.getValue());
}

System.out.println("备份完成: " + backupFile.getAbsolutePath());
System.out.println("校验和文件: " + checksumFile.getAbsolutePath());
System.out.println("CRC32: " + checksum.getValue());
}

/**
* 恢复备份并验证
*/
public static void restoreWithVerification(String backupPath, String checksumPath,
String restorePath) throws IOException {

// 读取存储的校验和
long storedChecksum;
try (DataInputStream dis = new DataInputStream(
new FileInputStream(checksumPath))) {
storedChecksum = dis.readLong();
}

// 恢复文件并计算校验和
Checksum checksum = new CRC32();

try (FileInputStream fis = new FileInputStream(backupPath);
CheckedInputStream cis = new CheckedInputStream(fis, checksum);
FileOutputStream fos = new FileOutputStream(restorePath)) {

byte[] buffer = new byte[8192];
int len;
while ((len = cis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}

long calculatedChecksum = checksum.getValue();

if (storedChecksum == calculatedChecksum) {
System.out.println("✓ 文件恢复成功完整性验证通过");
} else {
System.err.println("✗ 文件可能损坏存储: " + storedChecksum +
", 计算: " + calculatedChecksum);
new File(restorePath).delete();
}
}

public static void main(String[] args) {
try {
// 复制文件并验证
copyFileWithChecksum("source.txt", "copy.txt");

// 备份文件
backupWithChecksum("important.txt", "backup");

// 恢复备份
restoreWithVerification("backup/important.txt.bak",
"backup/important.txt.crc", "restored.txt");

} catch (IOException e) {
e.printStackTrace();
}
}
}

⭐⭐ 大文件分块校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import java.io.*;
import java.util.zip.*;

public class LargeFileChecksum {

/**
* 计算大文件的校验和使用缓冲流
*/
public static long calculateLargeFileChecksum(String filePath)
throws IOException {

Checksum checksum = new CRC32();

try (FileInputStream fis = new FileInputStream(filePath);
CheckedInputStream cis = new CheckedInputStream(
new BufferedInputStream(fis, 65536), checksum)) {

byte[] buffer = new byte[65536];
while (cis.read(buffer) != -1) {
// 持续读取校验和自动更新
}
}

return checksum.getValue();
}

/**
* 分块计算校验和用于超大文件
*/
public static List<Long> calculateBlockChecksums(String filePath, int blockSize)
throws IOException {

List<Long> checksums = new ArrayList<>();

try (FileInputStream fis = new FileInputStream(filePath);
BufferedInputStream bis = new BufferedInputStream(fis, 65536)) {

byte[] buffer = new byte[blockSize];
int blockNumber = 0;

while (true) {
Checksum checksum = new CRC32();
int bytesRead = bis.read(buffer);

if (bytesRead == -1) break;

// 更新校验和
checksum.update(buffer, 0, bytesRead);
checksums.add(checksum.getValue());

System.out.printf("块 %d: 读取 %d 字节, CRC: %d%n",
++blockNumber, bytesRead, checksum.getValue());
}
}

return checksums;
}

/**
* 验证大文件完整性使用块校验
*/
public static boolean verifyLargeFile(String filePath, List<Long> expectedChecksums,
int blockSize) throws IOException {

try (FileInputStream fis = new FileInputStream(filePath);
BufferedInputStream bis = new BufferedInputStream(fis, 65536)) {

byte[] buffer = new byte[blockSize];
int blockNumber = 0;

for (Long expectedChecksum : expectedChecksums) {
Checksum checksum = new CRC32();
int bytesRead = bis.read(buffer);

if (bytesRead == -1) {
System.err.println("文件结束但还有更多校验和");
return false;
}

checksum.update(buffer, 0, bytesRead);
long actualChecksum = checksum.getValue();

if (expectedChecksum != actualChecksum) {
System.err.printf("块 %d 校验失败期望: %d, 实际: %d%n",
blockNumber, expectedChecksum, actualChecksum);
return false;
}

System.out.printf("块 %d 验证通过%n", ++blockNumber);
}

// 检查是否还有多余数据
if (bis.read(buffer) != -1) {
System.err.println("文件有多余数据");
return false;
}

return true;
}
}

/**
* 分块备份大文件
*/
public static void backupLargeFile(String sourcePath, String backupDir,
int blockSize) throws IOException {

File backupDirectory = new File(backupDir);
backupDirectory.mkdirs();

List<Long> checksums = new ArrayList<>();

try (FileInputStream fis = new FileInputStream(sourcePath);
BufferedInputStream bis = new BufferedInputStream(fis, 65536)) {

byte[] buffer = new byte[blockSize];
int blockNumber = 0;

while (true) {
Checksum checksum = new CRC32();
int bytesRead = bis.read(buffer);

if (bytesRead == -1) break;

checksum.update(buffer, 0, bytesRead);
checksums.add(checksum.getValue());

// 保存块文件
String blockFile = backupDir + File.separator +
"block_" + String.format("%04d", ++blockNumber) + ".dat";

try (FileOutputStream fos = new FileOutputStream(blockFile)) {
fos.write(buffer, 0, bytesRead);
}

System.out.printf("备份块 %d: %s (CRC: %d)%n",
blockNumber, blockFile, checksum.getValue());
}
}

// 保存校验和清单
String checksumFile = backupDir + File.separator + "checksums.dat";
try (DataOutputStream dos = new DataOutputStream(
new FileOutputStream(checksumFile))) {

dos.writeInt(checksums.size());
for (Long crc : checksums) {
dos.writeLong(crc);
}
dos.writeInt(blockSize);
}

System.out.println("备份完成共 " + checksums.size() + " 个块");
}

public static void main(String[] args) {
try {
// 计算大文件校验和
long crc = calculateLargeFileChecksum("large_file.dat");
System.out.println("文件 CRC32: " + crc);

// 分块校验
List<Long> blockChecksums = calculateBlockChecksums("large_file.dat", 1024 * 1024);
System.out.println("分块校验完成共 " + blockChecksums.size() + " 块");

// 分块备份
backupLargeFile("large_file.dat", "backup_blocks", 1024 * 1024);

} catch (IOException e) {
e.printStackTrace();
}
}
}

并发编程

Java 并发编程是指在 Java 编程语言中编写能够同时执行多个任务的程序的技术它主要涉及多线程同步机制并发工具类和并发数据结构等内容目的是充分利用多核处理器的计算能力提高程序的执行效率和响应速度

进程与线程

  • 进程一个正在运行的程序实例是操作系统进行资源分配的基本单位它拥有自己独立的内存空间如堆方法区文件描述符等资源
  • 线程进程内部的一个执行单元是 CPU 进行任务调度的基本单位一个进程可以包含多个线程这些线程共享进程的资源但各自拥有独立的程序计数器虚拟机栈和本地方法栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 进程正在运行的程序实例拥有独立的内存空间
* 线程进程中的一个执行单元共享进程的内存空间
*/
public class ProcessVsThread {
// main 方法是主线程的执行入口
// 当 Java 程序启动时JVMJava 虚拟机会创建一个主线程然后在这个线程中调用 main 方法
public static void main(String[] args) {
// 获取当前线程信息
Thread currentThread = Thread.currentThread();
System.out.println("当前线程: " + currentThread.getName());
System.out.println("线程ID: " + currentThread.getId());
System.out.println("优先级: " + currentThread.getPriority());
System.out.println("状态: " + currentThread.getState());

// 获取所有线程
System.out.println("\n所有活动线程 (包含系统守护线程):");
for (Thread t : Thread.getAllStackTraces().keySet()) {
if (t != null) {
// 增加守护线程标识和线程组信息以便观察
String daemonFlag = t.isDaemon() ? "[守护]" : "[用户]";
System.out.println(" " + daemonFlag + " " + t.getName() + " - " + t.getState() + " (组:" + t.getThreadGroup().getName() + ")");
}
}
}
// 通常情况下main 方法里的代码执行完了主线程就结束了
// 但是Java 进程不会因为主线程结束而立刻结束它会等待所有用户线程User Thread即普通的子线程干完活后才会结束
// 主线程结束了用户线程也结束了只剩下守护线程在干活JVM 会直接关闭守护线程会被强制杀掉这时该 java 进程结束
}

生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 线程状态
* NEW - 新建状态
* RUNNABLE - 就绪/运行状态
* BLOCKED - 阻塞状态等待锁
* WAITING - 等待状态无限期等待
* TIMED_WAITING - 定时等待状态
* TERMINATED - 终止状态
*/
public class ThreadLifeCycle {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
System.out.println("线程运行中...");
Thread.sleep(2000); // TIMED_WAITING状态
synchronized (ThreadLifeCycle.class) {
ThreadLifeCycle.class.wait(); // WAITING状态
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});

// NEW状态
System.out.println("创建后: " + thread.getState());

// RUNNABLE状态
thread.start();
System.out.println("启动后: " + thread.getState());

// TIMED_WAITING状态
Thread.sleep(100);
System.out.println("sleep后: " + thread.getState());

// TERMINATED状态
thread.join();
System.out.println("结束后: " + thread.getState());
}
}

创建线程

⭐⭐ 继承 Thread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ExtendsThread extends Thread {
private String name;

public ExtendsThread(String name) {
this.name = name;
}

@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + " 执行: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) {
ExtendsThread t1 = new ExtendsThread("线程1");
ExtendsThread t2 = new ExtendsThread("线程2");

t1.start();
t2.start();

// 等待线程结束
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("主线程结束");
}
}

⭐⭐ 实现 Runnable 接口推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class ImplementsRunnable implements Runnable {
private String name;

public ImplementsRunnable(String name) {
this.name = name;
}

@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + " 执行: " + i + ", 线程: " + Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) {
// 创建任务
ImplementsRunnable task1 = new ImplementsRunnable("任务1");
ImplementsRunnable task2 = new ImplementsRunnable("任务2");

// 创建线程并执行
Thread thread1 = new Thread(task1, "线程A");
Thread thread2 = new Thread(task2, "线程B");

thread1.start();
thread2.start();

// 使用lambda表达式Java 8+
Thread thread3 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Lambda线程: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Lambda线程");
thread3.start();
}
}

⭐⭐ 实现 Callable 接口有返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.util.concurrent.*;

public class ImplementsCallable implements Callable<String> {
private String taskName;

public ImplementsCallable(String taskName) {
this.taskName = taskName;
}

@Override
public String call() throws Exception {
System.out.println(taskName + " 开始执行");
Thread.sleep(2000);
return taskName + " 执行完成返回值: " + System.currentTimeMillis();
}

public static void main(String[] args) throws Exception {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(3);

// 提交任务
Future<String> future1 = executor.submit(new ImplementsCallable("任务1"));
Future<String> future2 = executor.submit(new ImplementsCallable("任务2"));

// 获取结果阻塞
System.out.println(future1.get());
System.out.println(future2.get());

// 批量提交
CompletionService<String> completionService = new ExecutorCompletionService<>(executor);
for (int i = 0; i < 5; i++) {
final int taskId = i;
completionService.submit(() -> {
Thread.sleep((long)(Math.random() * 3000));
return "任务" + taskId + "完成";
});
}

// 按完成顺序获取结果
for (int i = 0; i < 5; i++) {
Future<String> future = completionService.take();
System.out.println(future.get());
}

executor.shutdown();
}
}

同步机制

⭐⭐ synchronized 关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public class SynchronizedDemo {
private int count = 0;

// 1. 同步实例方法
public synchronized void increment() {
count++;
}

// 2. 同步静态方法
public static synchronized void staticMethod() {
System.out.println("静态同步方法");
}

// 3. 同步代码块
public void add() {
synchronized (this) {
count++;
}
}

// 4. 使用不同锁对象
private final Object lock = new Object();

public void addWithLock() {
synchronized (lock) {
count++;
}
}

// 5. 生产者-消费者示例
private static class Buffer {
private final int[] data = new int[10];
private int count = 0;

public synchronized void produce(int value) throws InterruptedException {
while (count == data.length) {
System.out.println("缓冲区满生产者等待");
wait(); // 释放锁并等待
}
data[count++] = value;
System.out.println("生产: " + value + ", 数量: " + count);
notifyAll(); // 唤醒所有等待线程
}

public synchronized int consume() throws InterruptedException {
while (count == 0) {
System.out.println("缓冲区空消费者等待");
wait();
}
int value = data[--count];
System.out.println("消费: " + value + ", 剩余: " + count);
notifyAll();
return value;
}
}

public static void main(String[] args) {
Buffer buffer = new Buffer();

// 生产者线程
Thread producer = new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
buffer.produce(i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

// 消费者线程
Thread consumer = new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
buffer.consume();
Thread.sleep(150);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

producer.start();
consumer.start();
}
}

⭐⭐ Lock 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import java.util.concurrent.locks.*;
import java.util.concurrent.TimeUnit;

public class LockDemo {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private int count = 0;

// 基本使用
public void increment() {
lock.lock();
try {
count++;
System.out.println(Thread.currentThread().getName() + " 增加后: " + count);
} finally {
lock.unlock(); // 必须在finally中释放
}
}

// 尝试获取锁
public boolean tryIncrement() {
try {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
count++;
return true;
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}

// 可中断的锁获取
public void interruptibleIncrement() throws InterruptedException {
lock.lockInterruptibly();
try {
count++;
} finally {
lock.unlock();
}
}

// 公平锁示例
private final ReentrantLock fairLock = new ReentrantLock(true); // 公平锁

public void fairLockDemo() {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
fairLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获得锁");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
fairLock.unlock();
}
}, "线程" + i).start();
}
}

// 读写锁示例
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
private int sharedData = 0;

public void read() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 读取: " + sharedData);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
}

public void write(int value) {
writeLock.lock();
try {
sharedData = value;
System.out.println(Thread.currentThread().getName() + " 写入: " + value);
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}

public static void main(String[] args) {
LockDemo demo = new LockDemo();

// 测试读写锁
for (int i = 0; i < 3; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) {
demo.read();
}
}, "读线程" + i).start();
}

for (int i = 0; i < 2; i++) {
final int value = i;
new Thread(() -> {
for (int j = 0; j < 3; j++) {
demo.write(value);
}
}, "写线程" + i).start();
}
}
}

线程通信

⭐⭐ wait/notify 机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class WaitNotifyDemo {
private static class SharedResource {
private String message;
private boolean hasMessage = false;

public synchronized void produce(String message) throws InterruptedException {
while (hasMessage) {
System.out.println("等待消费...");
wait();
}
this.message = message;
hasMessage = true;
System.out.println("生产: " + message);
notify();
}

public synchronized String consume() throws InterruptedException {
while (!hasMessage) {
System.out.println("等待生产...");
wait();
}
String result = message;
hasMessage = false;
System.out.println("消费: " + result);
notify();
return result;
}
}

public static void main(String[] args) {
SharedResource resource = new SharedResource();

Thread producer = new Thread(() -> {
String[] messages = {"消息1", "消息2", "消息3", "消息4", "消息5"};
for (String msg : messages) {
try {
resource.produce(msg);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

Thread consumer = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
resource.consume();
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

producer.start();
consumer.start();
}
}

⭐⭐ Condition 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import java.util.concurrent.locks.*;
import java.util.LinkedList;
import java.util.Queue;

public class ConditionDemo {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity = 5;

public void produce(int value) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
System.out.println("队列满生产者等待");
notFull.await();
}
queue.offer(value);
System.out.println("生产: " + value + ", 队列大小: " + queue.size());
notEmpty.signal();
} finally {
lock.unlock();
}
}

public int consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
System.out.println("队列空消费者等待");
notEmpty.await();
}
int value = queue.poll();
System.out.println("消费: " + value + ", 队列大小: " + queue.size());
notFull.signal();
return value;
} finally {
lock.unlock();
}
}

public static void main(String[] args) {
ConditionDemo demo = new ConditionDemo();

// 多个生产者
for (int i = 0; i < 3; i++) {
final int producerId = i;
new Thread(() -> {
for (int j = 0; j < 10; j++) {
try {
demo.produce(producerId * 100 + j);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者" + i).start();
}

// 多个消费者
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 15; j++) {
try {
demo.consume();
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者" + i).start();
}
}
}

线程池

⭐⭐ 线程池的创建和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadPoolDemo {

public static void main(String[] args) {
// 1. 固定大小的线程池
ExecutorService fixedPool = Executors.newFixedThreadPool(5);

// 2. 单线程池
ExecutorService singlePool = Executors.newSingleThreadExecutor();

// 3. 缓存线程池
ExecutorService cachedPool = Executors.newCachedThreadPool();

// 4. 定时任务线程池
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);

// 5. 工作窃取线程池Java 8+
ExecutorService workStealingPool = Executors.newWorkStealingPool();

// 6. 自定义线程池
ThreadPoolExecutor customPool = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(10), // 任务队列
new ThreadFactory() { // 线程工厂
private final AtomicInteger threadNumber = new AtomicInteger(1);

@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "custom-pool-thread-" + threadNumber.getAndIncrement());
t.setDaemon(false);
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

// 使用线程池
Runnable task = () -> {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " 执行任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};

// 提交任务
for (int i = 0; i < 20; i++) {
customPool.execute(task);
}

// 提交有返回值的任务
Future<String> future = customPool.submit(() -> {
Thread.sleep(2000);
return "任务结果";
});

try {
System.out.println(future.get(3, TimeUnit.SECONDS));
} catch (Exception e) {
e.printStackTrace();
}

// 批量提交
CompletionService<String> completionService = new ExecutorCompletionService<>(customPool);
for (int i = 0; i < 10; i++) {
final int taskId = i;
completionService.submit(() -> {
Thread.sleep((long)(Math.random() * 3000));
return "任务" + taskId + "完成";
});
}

// 按完成顺序获取结果
for (int i = 0; i < 10; i++) {
try {
Future<String> result = completionService.take();
System.out.println(result.get());
} catch (Exception e) {
e.printStackTrace();
}
}

// 关闭线程池
customPool.shutdown(); // 不再接受新任务等待已提交任务执行完成
try {
if (!customPool.awaitTermination(60, TimeUnit.SECONDS)) {
customPool.shutdownNow(); // 强制关闭
}
} catch (InterruptedException e) {
customPool.shutdownNow();
}
}
}

⭐⭐ 自定义线程池配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class CustomThreadPoolConfig {

// 根据CPU核心数配置线程池
private static final int CPU_CORES = Runtime.getRuntime().availableProcessors();

// CPU密集型任务
public static ExecutorService cpuIntensivePool() {
return new ThreadPoolExecutor(
CPU_CORES,
CPU_CORES,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>()
);
}

// IO密集型任务
public static ExecutorService ioIntensivePool() {
int threadCount = CPU_CORES * 2;
return new ThreadPoolExecutor(
threadCount,
threadCount,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadPoolExecutor.CallerRunsPolicy()
);
}

// 监控线程池状态
public static class MonitoredThreadPool extends ThreadPoolExecutor {
public MonitoredThreadPool(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}

@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println("准备执行任务: " + t.getName());
System.out.println("活跃线程数: " + getActiveCount());
System.out.println("队列大小: " + getQueue().size());
}

@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println("任务执行完成");
System.out.println("已完成任务数: " + getCompletedTaskCount());
}

@Override
protected void terminated() {
System.out.println("线程池已终止");
}
}
}

并发集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

public class ConcurrentCollectionDemo {

public static void main(String[] args) throws InterruptedException {
// 1. ConcurrentHashMap - 线程安全的HashMap
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

// 原子操作
map.putIfAbsent("key", 1);
map.computeIfAbsent("key2", k -> 2);
map.merge("key3", 1, Integer::sum);

// 批量操作
map.forEach((k, v) -> System.out.println(k + "=" + v));

// 2. CopyOnWriteArrayList - 读多写少场景
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.addIfAbsent("C");

// 3. ConcurrentLinkedQueue - 无界非阻塞队列
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.offer("元素1");
String element = queue.poll();

// 4. BlockingQueue - 阻塞队列
ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(10);

// 生产者
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
blockingQueue.put(i); // 队列满时阻塞
System.out.println("生产: " + i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();

// 消费者
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
Integer value = blockingQueue.take(); // 队列空时阻塞
System.out.println("消费: " + value);
Thread.sleep(150);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();

// 5. DelayQueue - 延迟队列
DelayQueue<DelayedElement> delayQueue = new DelayQueue<>();
delayQueue.put(new DelayedElement("任务1", 3000));
delayQueue.put(new DelayedElement("任务2", 1000));

while (!delayQueue.isEmpty()) {
DelayedElement element2 = delayQueue.take();
System.out.println("执行: " + element2.getData() +
", 延迟: " + element2.getDelay(TimeUnit.MILLISECONDS) + "ms");
}

// 6. SynchronousQueue - 同步队列
SynchronousQueue<String> syncQueue = new SynchronousQueue<>();

// 必须同时有生产者和消费者
new Thread(() -> {
try {
syncQueue.put("数据"); // 等待消费者
System.out.println("数据已放入");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();

new Thread(() -> {
try {
Thread.sleep(1000);
String data = syncQueue.take(); // 等待生产者
System.out.println("数据已取出: " + data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}

static class DelayedElement implements Delayed {
private final String data;
private final long startTime;

public DelayedElement(String data, long delayMillis) {
this.data = data;
this.startTime = System.currentTimeMillis() + delayMillis;
}

@Override
public long getDelay(TimeUnit unit) {
long diff = startTime - System.currentTimeMillis();
return unit.convert(diff, TimeUnit.MILLISECONDS);
}

@Override
public int compareTo(Delayed o) {
return Long.compare(this.startTime, ((DelayedElement) o).startTime);
}

public String getData() {
return data;
}
}
}

原子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import java.util.concurrent.atomic.*;
import java.util.concurrent.CountDownLatch;

public class AtomicDemo {

public static void main(String[] args) throws InterruptedException {
// 1. 基本原子类
AtomicInteger atomicInt = new AtomicInteger(0);
AtomicLong atomicLong = new AtomicLong(0);
AtomicBoolean atomicBoolean = new AtomicBoolean(false);

// 原子操作
atomicInt.incrementAndGet(); // ++i
atomicInt.getAndIncrement(); // i++
atomicInt.addAndGet(5); // i += 5
atomicInt.compareAndSet(0, 10); // CAS操作

// 2. 原子数组
AtomicIntegerArray atomicArray = new AtomicIntegerArray(10);
atomicArray.incrementAndGet(0);
atomicArray.compareAndSet(0, 1, 5);

// 3. 原子引用
AtomicReference<String> atomicRef = new AtomicReference<>("初始值");
atomicRef.compareAndSet("初始值", "新值");

// 4. 原子字段更新器
class Person {
volatile int age;
}
Person person = new Person();
AtomicIntegerFieldUpdater<Person> ageUpdater =
AtomicIntegerFieldUpdater.newUpdater(Person.class, "age");
ageUpdater.incrementAndGet(person);

// 5. LongAdder - 高并发计数
LongAdder adder = new LongAdder();
LongAccumulator accumulator = new LongAccumulator(Long::sum, 0);

// 性能测试
int threadCount = 100;
int incrementsPerThread = 10000;
CountDownLatch latch = new CountDownLatch(threadCount);

// AtomicLong 测试
AtomicLong atomicLong2 = new AtomicLong(0);
long start1 = System.currentTimeMillis();
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
for (int j = 0; j < incrementsPerThread; j++) {
atomicLong2.incrementAndGet();
}
latch.countDown();
}).start();
}
latch.await();
long end1 = System.currentTimeMillis();
System.out.println("AtomicLong耗时: " + (end1 - start1) + "ms, 结果: " + atomicLong2.get());

// LongAdder 测试
CountDownLatch latch2 = new CountDownLatch(threadCount);
LongAdder longAdder = new LongAdder();
long start2 = System.currentTimeMillis();
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
for (int j = 0; j < incrementsPerThread; j++) {
longAdder.increment();
}
latch2.countDown();
}).start();
}
latch2.await();
long end2 = System.currentTimeMillis();
System.out.println("LongAdder耗时: " + (end2 - start2) + "ms, 结果: " + longAdder.sum());
}
}

常见问题

⭐⭐ 线程安全与并发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
public class ThreadSafeBestPractices {

// 1. 使用不可变对象
public static class ImmutablePerson {
private final String name;
private final int age;

public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() { return name; }
public int getAge() { return age; }
}

// 2. 使用ThreadLocal
public static class ThreadLocalExample {
private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

public String formatDate(Date date) {
return dateFormat.get().format(date);
}
}

// 3. 单例模式 - 双重检查锁定
public static class Singleton {
private static volatile Singleton instance;

private Singleton() {}

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

// 4. 使用并发工具类
public static class ConcurrentExample {
private final Semaphore semaphore = new Semaphore(3); // 限制并发数
private final CountDownLatch latch = new CountDownLatch(5); // 等待多个线程
private final CyclicBarrier barrier = new CyclicBarrier(3); // 同步屏障

public void useSemaphore() throws InterruptedException {
semaphore.acquire();
try {
// 执行需要限制并发的操作
} finally {
semaphore.release();
}
}

public void useCountDownLatch() throws InterruptedException {
// 主线程等待所有子线程完成
latch.await();
}

public void useCyclicBarrier() throws InterruptedException, BrokenBarrierException {
// 所有线程到达屏障后一起执行
barrier.await();
}
}

// 5. 避免死锁
public static class DeadlockPrevention {
private final Object lock1 = new Object();
private final Object lock2 = new Object();

// 错误示例 - 可能死锁
public void badMethod1() {
synchronized (lock1) {
synchronized (lock2) {
// 操作
}
}
}

public void badMethod2() {
synchronized (lock2) {
synchronized (lock1) {
// 操作
}
}
}

// 正确示例 - 固定锁顺序
public void goodMethod1() {
synchronized (lock1) {
synchronized (lock2) {
// 操作
}
}
}

public void goodMethod2() {
synchronized (lock1) {
synchronized (lock2) {
// 操作
}
}
}

// 使用tryLock避免死锁
public void tryLockMethod() throws InterruptedException {
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();

while (true) {
if (lock1.tryLock()) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
// 成功获取两个锁
break;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
Thread.sleep(100); // 等待后重试
}
}
}
}

⭐⭐ 线程安全问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class ThreadSafetyIssues {

// 问题1: 可见性问题
private static volatile boolean flag = false; // volatile保证可见性

// 问题2: 原子性问题
private static AtomicInteger counter = new AtomicInteger(0);

// 问题3: 指令重排序
private static class ReorderingIssue {
private int x = 0;
private int y = 0;
private volatile int a = 0; // volatile防止重排序
private volatile int b = 0;
}

// 解决方案
public static void main(String[] args) {
// 使用synchronized
synchronized (ThreadSafetyIssues.class) {
// 临界区代码
}

// 使用Lock
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}

// 使用原子类
counter.incrementAndGet();

// 使用并发集合
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

// 使用volatile保证可见性
flag = true;
}
}

⭐⭐ 性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class PerformanceOptimization {

// 1. 减少锁的粒度
public class FineGrainedLock {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
private int value1 = 0;
private int value2 = 0;

public void increment1() {
synchronized (lock1) {
value1++;
}
}

public void increment2() {
synchronized (lock2) {
value2++;
}
}
}

// 2. 读写锁
public class ReadWriteLockExample {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private int data = 0;

public int read() {
readLock.lock();
try {
return data;
} finally {
readLock.unlock();
}
}

public void write(int value) {
writeLock.lock();
try {
data = value;
} finally {
writeLock.unlock();
}
}
}

// 3. 使用无锁数据结构
public class LockFreeExample {
private final AtomicReference<Node> head = new AtomicReference<>();

private static class Node {
final int value;
Node next;
Node(int value) { this.value = value; }
}

public void push(int value) {
Node newNode = new Node(value);
Node oldHead;
do {
oldHead = head.get();
newNode.next = oldHead;
} while (!head.compareAndSet(oldHead, newNode));
}
}
}

图形界面

Java 的图形用户界面GUI工具包发展历程清晰主要经历了从 AWTSwingJavaFX 的演进目前JavaFX是官方主推的现代 GUI 框架Swing 仍在维护大量遗留系统

⭐⭐ AWT

AWT 是Java最早的GUI库随 JDK 1.0 于1995年发布它的核心设计思想是重量级即每个 Java 组件在底层操作系统中都有一个对应的由原生代码实现的对等体Peer

  • 优点
    • 内置可用作为java.awt包的一部分无需任何额外配置
    • 外观原生由于直接调用系统组件程序外观与当前操作系统保持一致
  • 缺点
    • 跨平台一致性差在不同操作系统上组件的外观和行为可能存在差异违背了Java一次编写到处运行的初衷
    • 功能有限提供的组件较少难以开发复杂的现代界面
    • 重量级对系统资源占用相对较高
  • 现状已不再用于新项目开发但它是Swing的基础其事件处理模型至今仍在使用

⭐⭐ Swing

为了解决 AWT 的缺陷Swing 在 JDK 1.2 中被引入它几乎完全用 Java 编写因此被称为轻量级组件

  • 优点
    • 跨平台一致性Swing绘制自己的组件不依赖原生API保证了在所有平台上外观一致
    • 丰富的组件集提供了比AWT更多的组件如表格JTableJTree
    • 可插拔的外观可以在运行时轻松改变程序的外观和感觉Look and Feel甚至模拟Windows或Mac的风格
  • 缺点
    • 外观复古默认外观较为传统难以打造极具现代感或高度定制化的精美界面
    • 性能开销相比直接调用原生API渲染速度可能稍慢
  • 现状仍是Java SE的一部分无需额外依赖在维护大量遗留企业级应用和开发内部工具时被广泛使用

⭐⭐ JavaFX

JavaFX 被设计为 Swing 的继任者从 Java 8 开始被纳入 JDKJDK 8u451 版本及之后被移除之后成为一个独立的开源项目OpenJFX需要像管理普通第三方库一样手动引入依赖之所以在 JDK 中移除 JavaFX并不是因为这项技术被放弃恰恰相反这是为了给它一个更自由更快的发展环境而做出的战略调整对于大量开发服务器端应用不需要图形界面的开发者来说JavaFX 成了无用的负担将其分离后JDK 回归为更纯粹的核心开发工具包如果开发者需要 JavaFX可以像引入其他第三方库一样按需添加使 JDK 更加轻量和专注

  • 优点
    • 现代化的UI支持CSS样式表可以轻松实现圆角阴影渐变等精美外观
    • 硬件加速其图形引擎Prism利用硬件加速动画和图形渲染性能更好
    • 强大的布局和特性提供了VBoxHBox等灵活布局并原生支持FXML一种XML格式允许像设计网页一样通过声明式代码分离界面和逻辑
    • 丰富的内置组件内置了图表WebView用于嵌入网页等高级控件
  • 缺点
    • 环境配置需要手动添加模块路径或通过Maven/Gradle管理依赖
  • 现状是Java GUI开发的官方推荐和未来方向适用于所有新项目特别是那些需要现代化界面和丰富交互的应用

架构图

主体架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
图形用户界面

├── 1. GUI 组件体系
│ ├── AWT 组件 (重量级)
│ └── Swing 组件 (轻量级)

├── 2. 图形与颜色体系
│ ├── 颜色 (Color, SystemColor)
│ ├── 图形 (Graphics, Graphics2D)
│ ├── 画笔 (Paint, Stroke, Composite)
│ ├── 形状 (Shape, Area)
│ └── 字体 (Font, FontMetrics, TextLayout)

├── 3. 图像处理体系
│ ├── 图像 (Image, BufferedImage)
│ ├── 图标 (ImageIcon)
│ ├── 读写 (ImageIO)
│ └── 滤镜 (BufferedImageOp)

├── 4. 布局管理器体系
│ ├── 基本布局 (Flow, Border, Grid, Card)
│ ├── 高级布局 (GridBag, Box, Group)
│ └── 自定义布局 (LayoutManager)

├── 5. 事件处理体系
│ ├── 事件对象 (EventObject)
│ ├── 事件类型 (Action, Mouse, Key, Window...)
│ ├── 监听器接口 (EventListener)
│ └── 适配器类 (Adapter)

├── 6. 打印体系
│ ├── 打印任务 (PrinterJob)
│ ├── 打印接口 (Printable, Pageable)
│ └── 打印服务 (javax.print)

├── 7. 媒体与音频体系
│ ├── 音频片段 (AudioClip)
│ ├── 采样音频 (javax.sound.sampled)
│ └── MIDI音频 (javax.sound.midi)

├── 8. 辅助技术体系
│ ├── 可访问性 (Accessible)
│ └── 焦点管理 (KeyboardFocusManager)

├── 9. 拖放与剪贴板体系
│ ├── 拖放 (Drag and Drop)
│ └── 剪贴板 (Clipboard)

├── 10. 输入法体系
│ ├── 输入上下文 (InputContext)
│ └── 输入法请求 (InputMethodRequest)

├── 11. 外观与主题体系
│ ├── 外观管理 (UIManager)
│ ├── 外观实现 (LookAndFeel)
│ ├── UI委托 (ComponentUI)
│ └── 边框 (Border)

└── 12. 其他工具类
├── Toolkit / Robot
├── Cursor / Point / Dimension / Rectangle / Insets
├── 渲染提示 (RenderingHints)
└── 并发工具 (SwingWorker)

组件相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
java.lang.Object

└── java.awt.Component (所有 GUI 组件的抽象基类)

├── AWT 组件体系 (重量级组件)
│ │
│ ├── 1. 基本组件
│ │ ├── java.awt.Button (按钮)
│ │ ├── java.awt.Label (标签)
│ │ ├── java.awt.TextComponent (文本组件基类)
│ │ │ ├── java.awt.TextField (单行文本框)
│ │ │ └── java.awt.TextArea (多行文本框)
│ │ ├── java.awt.List (列表)
│ │ ├── java.awt.Choice (下拉选择框)
│ │ ├── java.awt.Checkbox (复选框/单选按钮)
│ │ ├── java.awt.Scrollbar (滚动条)
│ │ └── java.awt.Canvas (绘图画布)
│ │
│ └── 2. 容器组件
│ └── java.awt.Container (容器基类)
│ │
│ ├── java.awt.Panel (面板容器)
│ │ └── java.applet.Applet (Applet容器)
│ │
│ ├── java.awt.ScrollPane (滚动面板)
│ │
│ └── java.awt.Window (窗口基类)
│ │
│ ├── java.awt.Frame (主窗口)
│ │ └── javax.swing.JFrame (Swing主窗口)
│ │
│ └── java.awt.Dialog (对话框基类)
│ ├── java.awt.FileDialog (文件对话框)
│ └── javax.swing.JDialog (Swing对话框)

└── Swing 组件体系 (轻量级组件)

└── javax.swing.JComponent (Swing组件基类)

├── 1. 按钮类组件
│ ├── JButton (普通按钮)
│ ├── JToggleButton (切换按钮)
│ │ ├── JCheckBox (复选框)
│ │ └── JRadioButton (单选按钮)
│ └── JMenuItem (菜单项)
│ ├── JMenu (菜单)
│ ├── JCheckBoxMenuItem (复选框菜单项)
│ └── JRadioButtonMenuItem (单选按钮菜单项)

├── 2. 文本类组件
│ ├── JLabel (标签)
│ ├── JTextField (单行文本框)
│ │ ├── JPasswordField (密码框)
│ │ └── JFormattedTextField (格式化文本框)
│ ├── JTextArea (多行文本框)
│ │ └── JTextPane (样式化文本窗格)
│ └── JEditorPane (编辑器窗格)

├── 3. 选择类组件
│ ├── JComboBox (下拉组合框)
│ ├── JList (列表)
│ ├── JSpinner (数值选择器)
│ ├── JSlider (滑块)
│ └── JProgressBar (进度条)

├── 4. 容器类组件
│ ├── JPanel (通用面板)
│ ├── JScrollPane (滚动面板)
│ ├── JSplitPane (分割面板)
│ ├── JTabbedPane (选项卡面板)
│ ├── JToolBar (工具栏)
│ ├── JLayeredPane (分层面板)
│ │ └── JDesktopPane (桌面面板 - MDI容器)
│ └── JRootPane (根面板)

├── 5. 高级组件
│ ├── JTable (表格)
│ ├── JTree (树)
│ ├── JFileChooser (文件选择器)
│ └── JColorChooser (颜色选择器)

├── 6. 菜单类组件
│ ├── JMenuBar (菜单栏)
│ ├── JMenu (菜单)
│ ├── JMenuItem (菜单项)
│ ├── JCheckBoxMenuItem (复选框菜单项)
│ ├── JRadioButtonMenuItem (单选按钮菜单项)
│ └── JPopupMenu (弹出菜单)

├── 7. 窗口类组件
│ ├── JFrame (主窗口) - 继承自Frame
│ ├── JDialog (对话框) - 继承自Dialog
│ ├── JApplet (Applet) - 继承自Applet
│ └── JInternalFrame (内部框架)

└── 8. 其他组件
├── JSeparator (分隔线)
├── JToolTip (工具提示)
├── JScrollBar (滚动条)
├── JProgressBar (进度条)
└── JSpinner (微调按钮)

颜色相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
java.lang.Object

├── java.awt.Color (RGB/HSB 颜色)
│ │
│ ├── 预定义颜色常量
│ │ ├── Color.WHITE (白色) - rgb(255,255,255)
│ │ ├── Color.BLACK (黑色) - rgb(0,0,0)
│ │ ├── Color.RED (红色) - rgb(255,0,0)
│ │ ├── Color.GREEN (绿色) - rgb(0,255,0)
│ │ ├── Color.BLUE (蓝色) - rgb(0,0,255)
│ │ ├── Color.YELLOW (黄色) - rgb(255,255,0)
│ │ ├── Color.CYAN (青色) - rgb(0,255,255)
│ │ ├── Color.MAGENTA (品红) - rgb(255,0,255)
│ │ ├── Color.ORANGE (橙色) - rgb(255,200,0)
│ │ ├── Color.PINK (粉色) - rgb(255,175,175)
│ │ ├── Color.GRAY (灰色) - rgb(128,128,128)
│ │ ├── Color.DARK_GRAY (深灰) - rgb(64,64,64)
│ │ ├── Color.LIGHT_GRAY (浅灰) - rgb(192,192,192)
│ │ └── Color.TRANSLUCENT (透明) - rgba(0,0,0,0)
│ │
│ ├── 构造方式
│ │ ├── Color(int r, int g, int b) - RGB 分量 (0-255)
│ │ ├── Color(int r, int g, int b, int a) - RGBA 分量
│ │ ├── Color(int rgb) - 32位RGB值 (0xRRGGBB)
│ │ ├── Color(int rgba, boolean hasalpha) - 32位RGBA值
│ │ ├── Color(float r, float g, float b) - RGB 分量 (0.0-1.0)
│ │ └── Color(ColorSpace cspace, float[] comps, float alpha) - 颜色空间
│ │
│ └── 静态工厂方法
│ ├── getColor(String name) - 从系统属性获取颜色
│ ├── getHSBColor(float h, float s, float b) - HSB 转 RGB
│ ├── decode(String hex) - 十六进制字符串解析 (#RRGGBB)
│ └── HSBtoRGB(float h, float s, float b) - HSB 转 RGB 值

└── java.awt.SystemColor (系统主题颜色)

├── SystemColor.control (控件背景色)
├── SystemColor.controlText (控件文本色)
├── SystemColor.text (文本背景色)
├── SystemColor.textText (文本前景色)
├── SystemColor.window (窗口背景色)
├── SystemColor.windowText (窗口文本色)
├── SystemColor.menu (菜单背景色)
├── SystemColor.menuText (菜单文本色)
├── SystemColor.activeCaption (活动标题栏)
├── SystemColor.activeCaptionText (活动标题栏文本)
├── SystemColor.inactiveCaption (非活动标题栏)
├── SystemColor.inactiveCaptionText (非活动标题栏文本)
├── SystemColor.desktop (桌面背景)
├── SystemColor.info (信息提示背景)
└── SystemColor.infoText (信息提示文本)

图形相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
java.lang.Object

├── java.awt.Graphics (抽象图形上下文)
│ │
│ └── java.awt.Graphics2D (增强2D图形上下文)
│ │
│ ├── 图形绘制方法
│ │ ├── draw(Shape s) - 绘制形状轮廓
│ │ ├── fill(Shape s) - 填充形状
│ │ ├── drawLine(x1,y1,x2,y2) - 绘制直线
│ │ ├── drawRect(x,y,w,h) - 绘制矩形轮廓
│ │ ├── fillRect(x,y,w,h) - 填充矩形
│ │ ├── drawRoundRect(x,y,w,h,aw,ah) - 绘制圆角矩形轮廓
│ │ ├── fillRoundRect(x,y,w,h,aw,ah) - 填充圆角矩形
│ │ ├── drawOval(x,y,w,h) - 绘制椭圆轮廓
│ │ ├── fillOval(x,y,w,h) - 填充椭圆
│ │ ├── drawArc(x,y,w,h,sa,aa) - 绘制弧形
│ │ ├── fillArc(x,y,w,h,sa,aa) - 填充扇形
│ │ ├── drawPolygon(xPoints,yPoints,n) - 绘制多边形轮廓
│ │ ├── fillPolygon(xPoints,yPoints,n) - 填充多边形
│ │ └── drawString(str,x,y) - 绘制文本
│ │
│ ├── 图形属性设置
│ │ ├── setColor(Color c) - 设置颜色
│ │ ├── setFont(Font f) - 设置字体
│ │ ├── setPaint(Paint p) - 设置画笔
│ │ ├── setStroke(Stroke s) - 设置笔触
│ │ ├── setComposite(Composite c) - 设置混合模式
│ │ ├── setTransform(AffineTransform tx) - 设置变换
│ │ └── setRenderingHint(key,value) - 设置渲染提示
│ │
│ ├── 坐标变换
│ │ ├── translate(x,y) - 平移
│ │ ├── rotate(theta) - 旋转
│ │ ├── scale(sx,sy) - 缩放
│ │ └── shear(shx,shy) - 切变
│ │
│ └── 图像绘制
│ ├── drawImage(Image img, x, y, observer) - 绘制图像
│ └── drawImage(Image img, x, y, w, h, observer) - 缩放绘制

├── java.awt.Shape (形状接口)
│ │ │
│ │ └── 定义几何形状的轮廓
│ │
│ └── java.awt.geom.* (2D几何图形包)
│ │
│ ├── 基本形状
│ │ ├── java.awt.geom.Line2D (直线)
│ │ │ ├── Line2D.Float (浮点坐标直线)
│ │ │ └── Line2D.Double (双精度直线)
│ │ │
│ │ ├── java.awt.geom.Rectangle2D (矩形)
│ │ │ ├── Rectangle2D.Float
│ │ │ └── Rectangle2D.Double
│ │ │ └── java.awt.Rectangle (整数矩形)
│ │ │
│ │ ├── java.awt.geom.RoundRectangle2D (圆角矩形)
│ │ │ ├── RoundRectangle2D.Float
│ │ │ └── RoundRectangle2D.Double
│ │ │
│ │ ├── java.awt.geom.Ellipse2D (椭圆)
│ │ │ ├── Ellipse2D.Float
│ │ │ └── Ellipse2D.Double
│ │ │
│ │ ├── java.awt.geom.Arc2D (弧形)
│ │ │ ├── Arc2D.Float
│ │ │ ├── Arc2D.Double
│ │ │ └── Arc2D.OPEN / CHORD / PIE (弧形类型)
│ │ │
│ │ ├── java.awt.geom.QuadCurve2D (二次曲线)
│ │ │ ├── QuadCurve2D.Float
│ │ │ └── QuadCurve2D.Double
│ │ │
│ │ └── java.awt.geom.CubicCurve2D (三次曲线)
│ │ ├── CubicCurve2D.Float
│ │ └── CubicCurve2D.Double
│ │
│ ├── 复合形状
│ │ ├── java.awt.geom.GeneralPath (通用路径)
│ │ │ └── 支持 moveTo, lineTo, quadTo, curveTo 等操作
│ │ │
│ │ ├── java.awt.geom.Path2D (路径2D)
│ │ │ ├── Path2D.Float
│ │ │ └── Path2D.Double
│ │ │
│ │ └── java.awt.geom.Area (区域)
│ │ ├── add(Area) - 并集
│ │ ├── subtract(Area) - 差集
│ │ ├── intersect(Area) - 交集
│ │ └── exclusiveOr(Area) - 异或
│ │
│ └── 辅助类
│ ├── java.awt.geom.Point2D (点)
│ │ ├── Point2D.Float
│ │ └── Point2D.Double
│ │ └── java.awt.Point (整数点)
│ │
│ ├── java.awt.geom.Dimension2D (尺寸)
│ │ └── java.awt.Dimension (整数尺寸)
│ │
│ └── java.awt.geom.AffineTransform (仿射变换)
│ ├── translate(x,y) - 平移
│ ├── rotate(theta) - 旋转
│ ├── scale(sx,sy) - 缩放
│ ├── shear(shx,shy) - 切变
│ └── concatenate(tx) - 变换连接

├── java.awt.GraphicsConfigTemplate (图形配置模板)
│ └── java.awt.GraphicsConfiguration (图形配置)

└── java.awt.GraphicsDevice (图形设备)
├── GraphicsDevice.TYPE_RASTER_SCREEN (屏幕设备)
├── GraphicsDevice.TYPE_PRINTER (打印设备)
└── GraphicsDevice.TYPE_IMAGE_BUFFER (图像缓冲设备)

布局相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java.lang.Object

├── 1. 核心接口
│ ├── java.awt.LayoutManager (布局管理器接口)
│ └── java.awt.LayoutManager2 (增强布局管理器接口)

├── 2. AWT 布局管理器
│ ├── java.awt.FlowLayout (流式布局)
│ ├── java.awt.BorderLayout (边界布局)
│ ├── java.awt.GridLayout (网格布局)
│ ├── java.awt.CardLayout (卡片布局)
│ └── java.awt.GridBagLayout (网格包布局)

├── 3. Swing 布局管理器
│ ├── javax.swing.BoxLayout (盒式布局)
│ ├── javax.swing.GroupLayout (组布局)
│ ├── javax.swing.SpringLayout (弹簧布局)
│ └── javax.swing.OverlayLayout (覆盖布局)

└── 4. 其他布局
├── java.awt.GridBagConstraints (网格包约束)
├── javax.swing.Box (盒式容器)
└── javax.swing.Box.Filler (填充组件)

事件相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
事件处理体系

├── 事件对象类型
│ │
│ ├── java.util.EventObject (事件基类) - 所有事件对象的顶层父类封装事件源对象
│ │
│ ├── java.awt.event.ActionEvent (动作事件) - 按钮点击菜单选择文本框回车等触发
│ │
│ ├── java.awt.event.MouseEvent (鼠标事件) - 鼠标点击移动拖拽进入/离开组件等
│ │
│ ├── java.awt.event.KeyEvent (键盘事件) - 键盘按键按下释放输入字符等
│ │
│ ├── java.awt.event.WindowEvent (窗口事件) - 窗口打开关闭激活最小化等
│ │
│ ├── java.awt.event.ItemEvent (项目事件) - 复选框单选按钮下拉列表等选项变化
│ │
│ ├── java.awt.event.AdjustmentEvent (调整事件) - 滚动条移动滑块位置改变
│ │
│ ├── java.awt.event.FocusEvent (焦点事件) - 组件获得焦点失去焦点
│ │
│ ├── java.awt.event.ComponentEvent (组件事件) - 组件移动大小改变显示/隐藏
│ │ │
│ │ ├── java.awt.event.ContainerEvent (容器事件) - 容器中添加或移除组件
│ │ │
│ │ └── java.awt.event.InputEvent (输入事件) - 输入设备事件的抽象基类
│ │ │
│ │ ├── java.awt.event.MouseEvent (鼠标事件) - 继承自InputEvent包含鼠标按钮点击次数修饰键等信息
│ │ │
│ │ └── java.awt.event.KeyEvent (键盘事件) - 继承自InputEvent包含按键码字符修饰键等信息
│ │
│ ├── java.awt.event.TextEvent (文本事件) - 文本组件内容发生变化
│ │
│ ├── java.awt.event.HierarchyEvent (层次事件) - 组件层次结构发生变化
│ │
│ ├── javax.swing.event.DocumentEvent (文档事件) - 文本组件文档内容改变Swing
│ │
│ ├── javax.swing.event.ListSelectionEvent (列表选择事件) - 列表或表格选中项改变Swing
│ │
│ ├── javax.swing.event.TreeSelectionEvent (树选择事件) - 树组件节点选中状态改变Swing
│ │
│ ├── javax.swing.event.TableModelEvent (表格模型事件) - 表格数据模型发生变化Swing
│ │
│ ├── javax.swing.event.CaretEvent (光标事件) - 文本组件光标位置改变Swing
│ │
│ ├── javax.swing.event.PopupMenuEvent (弹出菜单事件) - 弹出菜单显示或隐藏Swing
│ │
│ ├── javax.swing.event.AncestorEvent (祖先事件) - 组件添加或移除到容器层次时触发Swing
│ │
│ └── java.beans.PropertyChangeEvent (属性变化事件) - JavaBean组件属性值发生变化

├── 监听器
│ │
│ ├── java.util.EventListener (监听器标记接口) - 所有事件监听器的顶层标记接口
│ │
│ ├── java.awt.event.ActionListener (动作监听器) - 处理按钮点击菜单选择等动作事件
│ │
│ ├── java.awt.event.MouseListener (鼠标监听器) - 处理鼠标点击进入离开按下释放
│ │
│ ├── java.awt.event.MouseMotionListener (鼠标移动监听器) - 处理鼠标移动拖拽
│ │
│ ├── java.awt.event.MouseWheelListener (鼠标滚轮监听器) - 处理鼠标滚轮滚动
│ │
│ ├── java.awt.event.KeyListener (键盘监听器) - 处理按键按下释放输入字符
│ │
│ ├── java.awt.event.WindowListener (窗口监听器) - 处理窗口打开关闭激活最小化
│ │
│ ├── java.awt.event.WindowFocusListener (窗口焦点监听器) - 处理窗口获得或失去焦点
│ │
│ ├── java.awt.event.WindowStateListener (窗口状态监听器) - 处理窗口最大化最小化等状态变化
│ │
│ ├── java.awt.event.ItemListener (项目监听器) - 处理复选框单选按钮下拉列表选项变化
│ │
│ ├── java.awt.event.AdjustmentListener (调整监听器) - 处理滚动条滑块位置变化
│ │
│ ├── java.awt.event.FocusListener (焦点监听器) - 处理组件获得或失去焦点
│ │
│ ├── java.awt.event.ComponentListener (组件监听器) - 处理组件移动大小改变显示隐藏
│ │
│ ├── java.awt.event.ContainerListener (容器监听器) - 处理容器中添加或移除组件
│ │
│ ├── java.awt.event.TextListener (文本监听器) - 处理文本组件内容变化
│ │
│ ├── java.awt.event.HierarchyListener (层次监听器) - 处理组件层次结构变化
│ │
│ ├── java.awt.event.HierarchyBoundsListener (层次边界监听器) - 处理组件层次中祖先位置大小变化
│ │
│ ├── java.awt.event.InputMethodListener (输入法监听器) - 处理输入法文本变化
│ │
│ ├── javax.swing.event.DocumentListener (文档监听器) - 处理文本组件文档内容的插入删除修改
│ │
│ ├── javax.swing.event.ListSelectionListener (列表选择监听器) - 处理列表表格选中项变化
│ │
│ ├── javax.swing.event.TreeSelectionListener (树选择监听器) - 处理树节点选中状态变化
│ │
│ ├── javax.swing.event.TreeExpansionListener (树展开监听器) - 处理树节点展开或折叠
│ │
│ ├── javax.swing.event.TableModelListener (表格模型监听器) - 处理表格数据模型变化
│ │
│ ├── javax.swing.event.CaretListener (光标监听器) - 处理文本组件光标位置变化
│ │
│ ├── javax.swing.event.PopupMenuListener (弹出菜单监听器) - 处理弹出菜单显示隐藏取消
│ │
│ ├── javax.swing.event.AncestorListener (祖先监听器) - 处理组件被添加到容器或从容器移除
│ │
│ ├── javax.swing.event.ChangeListener (变化监听器) - 处理滑块进度条选项卡等状态变化
│ │
│ ├── javax.swing.event.HyperlinkListener (超链接监听器) - 处理编辑器窗格中超链接点击
│ │
│ ├── javax.swing.event.InternalFrameListener (内部框架监听器) - 处理内部框架打开关闭激活等
│ │
│ └── java.beans.PropertyChangeListener (属性变化监听器) - 处理JavaBean组件属性值变化

└── 适配器

├── java.awt.event.WindowAdapter (窗口适配器)

├── java.awt.event.MouseAdapter (鼠标适配器)

├── java.awt.event.KeyAdapter (键盘适配器)

├── java.awt.event.FocusAdapter (焦点适配器)

├── java.awt.event.ComponentAdapter (组件适配器)

├── java.awt.event.ContainerAdapter (容器适配器)

├── java.awt.event.HierarchyBoundsAdapter (层次边界适配器)

└── javax.swing.event.InternalFrameAdapter (内部框架适配器)

媒体相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java.lang.Object

├── 1. 音频播放体系
│ ├── java.applet.AudioClip (音频片段接口)
│ └── javax.sound.sampled.* (采样音频)

├── 2. MIDI 音频体系
│ └── javax.sound.midi.* (MIDI音频)

├── 3. 音频输入输出体系
│ ├── javax.sound.sampled.SourceDataLine (播放)
│ └── javax.sound.sampled.TargetDataLine (录音)

├── 4. 音频格式与转换体系
│ └── javax.sound.sampled.AudioFormat (音频格式)

├── 5. 音频文件读写体系
│ └── javax.sound.sampled.AudioSystem (音频系统)

├── 6. 音效与处理体系
│ └── javax.sound.sampled.spi.* (服务提供者接口)

└── 7. 系统提示音体系
└── java.awt.Toolkit.beep() (系统提示音)

外观相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
java.lang.Object

├── javax.swing.UIManager (外观管理器)
│ │
│ └── 核心静态方法
│ │
│ ├── 外观设置
│ │ ├── setLookAndFeel(LookAndFeel) - 设置外观
│ │ ├── setLookAndFeel(String className) - 通过类名设置
│ │ └── getLookAndFeel() - 获取当前外观
│ │
│ ├── 外观获取
│ │ ├── getInstalledLookAndFeels() - 获取已安装外观列表
│ │ ├── getSystemLookAndFeelClassName() - 获取系统外观类名
│ │ └── getCrossPlatformLookAndFeelClassName() - 获取跨平台外观类名
│ │
│ ├── 资源管理
│ │ ├── get(Object key) - 获取UI资源
│ │ ├── getColor(Object key) - 获取颜色
│ │ ├── getFont(Object key) - 获取字体
│ │ ├── getIcon(Object key) - 获取图标
│ │ ├── put(Object key, Object value) - 设置UI资源
│ │ └── getDefaults() - 获取默认资源表
│ │
│ ├── 组件更新
│ │ └── updateComponentUI(Component) - 更新组件UI
│ │
│ └── 外观切换
│ └── UIManager.setLookAndFeel() + SwingUtilities.updateComponentTreeUI(Component)

├── javax.swing.LookAndFeel (外观抽象类)
│ │
│ ├── 核心方法
│ │ ├── getName() - 获取外观名称
│ │ ├── getID() - 获取外观ID
│ │ ├── getDescription() - 获取外观描述
│ │ ├── isNativeLookAndFeel() - 是否本地外观
│ │ ├── isSupportedLookAndFeel() - 是否支持当前平台
│ │ │
│ │ ├── getDefaults() - 获取默认资源表
│ │ ├── getLayoutStyle() - 获取布局样式
│ │ │
│ │ ├── initialize() - 初始化外观
│ │ └── uninitialize() - 反初始化外观
│ │
│ ├── 静态辅助方法
│ │ ├── installColors(JComponent, String bgKey, String fgKey) - 安装颜色
│ │ ├── makeIcon(Class, String) - 创建图标
│ │ └── installBorder(JComponent, String) - 安装边框
│ │
│ └── 子类实现
│ ├── javax.swing.plaf.basic.BasicLookAndFeel (基础外观)
│ ├── javax.swing.plaf.metal.MetalLookAndFeel (Metal外观)
│ ├── javax.swing.plaf.nimbus.NimbusLookAndFeel (Nimbus外观)
│ ├── com.sun.java.swing.plaf.windows.WindowsLookAndFeel (Windows外观)
│ └── com.apple.laf.AquaLookAndFeel (Mac外观)

├── javax.swing.plaf.ComponentUI (组件UI基类)
│ │
│ ├── 核心方法
│ │ ├── createUI(JComponent) - 创建UI实例
│ │ ├── installUI(JComponent) - 安装UI
│ │ ├── uninstallUI(JComponent) - 卸载UI
│ │ │
│ │ ├── paint(Graphics, JComponent) - 绘制组件
│ │ ├── update(Graphics, JComponent) - 更新组件
│ │ │
│ │ ├── getPreferredSize(JComponent) - 获取首选大小
│ │ ├── getMinimumSize(JComponent) - 获取最小大小
│ │ └── getMaximumSize(JComponent) - 获取最大大小
│ │
│ └── 子类UI (按组件类型)
│ │
│ ├── 按钮UI
│ │ ├── javax.swing.plaf.ButtonUI
│ │ ├── javax.swing.plaf.basic.BasicButtonUI
│ │ └── javax.swing.plaf.metal.MetalButtonUI
│ │
│ ├── 标签UI
│ │ ├── javax.swing.plaf.LabelUI
│ │ └── javax.swing.plaf.basic.BasicLabelUI
│ │
│ ├── 文本UI
│ │ ├── javax.swing.plaf.TextUI
│ │ ├── javax.swing.plaf.basic.BasicTextFieldUI
│ │ └── javax.swing.plaf.metal.MetalTextFieldUI
│ │
│ ├── 表格UI
│ │ ├── javax.swing.plaf.TableUI
│ │ ├── javax.swing.plaf.basic.BasicTableUI
│ │ └── javax.swing.plaf.metal.MetalTableUI
│ │
│ ├── 树UI
│ │ ├── javax.swing.plaf.TreeUI
│ │ └── javax.swing.plaf.basic.BasicTreeUI
│ │
│ ├── 列表UI
│ │ ├── javax.swing.plaf.ListUI
│ │ └── javax.swing.plaf.basic.BasicListUI
│ │
│ ├── 滚动窗格UI
│ │ ├── javax.swing.plaf.ScrollPaneUI
│ │ └── javax.swing.plaf.basic.BasicScrollPaneUI
│ │
│ ├── 滑块UI
│ │ ├── javax.swing.plaf.SliderUI
│ │ └── javax.swing.plaf.basic.BasicSliderUI
│ │
│ ├── 进度条UI
│ │ ├── javax.swing.plaf.ProgressBarUI
│ │ └── javax.swing.plaf.basic.BasicProgressBarUI
│ │
│ ├── 选项卡UI
│ │ ├── javax.swing.plaf.TabbedPaneUI
│ │ └── javax.swing.plaf.basic.BasicTabbedPaneUI
│ │
│ └── 菜单UI
│ ├── javax.swing.plaf.MenuBarUI
│ ├── javax.swing.plaf.MenuItemUI
│ └── javax.swing.plaf.basic.BasicMenuBarUI

└── javax.swing.border.Border (边框接口)

├── 核心方法
│ ├── paintBorder(Component, Graphics, int, int, int, int) - 绘制边框
│ ├── getBorderInsets(Component) - 获取边框内边距
│ └── isBorderOpaque() - 是否不透明

└── 边框实现类

├── 1. 线条边框
│ └── javax.swing.border.LineBorder
│ ├── LineBorder(Color) - 单色线条
│ ├── LineBorder(Color, int) - 指定粗细
│ └── LineBorder(Color, int, boolean) - 圆角可选

├── 2. 斜面边框
│ ├── javax.swing.border.BevelBorder
│ │ ├── RAISED - 凸起斜面
│ │ └── LOWERED - 凹陷斜面
│ │
│ └── javax.swing.border.SoftBevelBorder
│ └── 柔化斜面效果

├── 3. 蚀刻边框
│ └── javax.swing.border.EtchedBorder
│ ├── RAISED - 凸起蚀刻
│ └── LOWERED - 凹陷蚀刻

├── 4. 标题边框
│ └── javax.swing.border.TitledBorder
│ ├── 标题位置: TOP, BOTTOM, LEFT, RIGHT
│ ├── 标题对齐: LEFT, CENTER, RIGHT
│ └── 标题字体: 可自定义

├── 5. 复合边框
│ └── javax.swing.border.CompoundBorder
│ ├── CompoundBorder(Border outside, Border inside)
│ └── 组合多个边框

├── 6. 空边框
│ └── javax.swing.border.EmptyBorder
│ └── EmptyBorder(int top, int left, int bottom, int right)

├── 7. 材质边框
│ └── javax.swing.border.MatteBorder
│ ├── MatteBorder(int, int, int, int, Color)
│ ├── MatteBorder(int, int, int, int, Icon)
│ └── 支持颜色或图标填充

└── 8. 抽象边框
└── javax.swing.border.AbstractBorder
├── 边框基类
└── 扩展自定义边框

组件容器

主窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import javax.swing.*;
import java.awt.*;

public class JFrameExample {
public static void main(String[] args) {
// 创建 JFrame
JFrame frame = new JFrame("Swing 示例");

// 设置关闭操作
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

// 设置大小
frame.setSize(400, 300);

// 设置位置
frame.setLocationRelativeTo(null); // 居中显示

// 设置布局管理器
frame.setLayout(new FlowLayout());

// 添加组件
JButton button = new JButton("点击我");
frame.add(button);

// 设置可见性
frame.setVisible(true);
}
}

对话框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JDialogExample {
public static void main(String[] args) {
JFrame parent = new JFrame("父窗口");
parent.setSize(300, 200);
parent.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

// 创建模态对话框
JDialog dialog = new JDialog(parent, "对话框", true);
dialog.setSize(200, 150);
dialog.setLocationRelativeTo(parent);

dialog.add(new JLabel("这是一个模态对话框"));

JButton showBtn = new JButton("显示对话框");
showBtn.addActionListener(e -> dialog.setVisible(true));
parent.add(showBtn);

parent.setVisible(true);
}
}

面板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class JPanelExample {
public static void main(String[] args) {
JFrame frame = new JFrame("JPanel 示例");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);

// 创建面板
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(2, 2));
panel.setBackground(Color.LIGHT_GRAY);
panel.setBorder(BorderFactory.createTitledBorder("控制面板"));

// 添加组件到面板
panel.add(new JButton("按钮1"));
panel.add(new JButton("按钮2"));
panel.add(new JButton("按钮3"));
panel.add(new JButton("按钮4"));

frame.add(panel);
frame.setVisible(true);
}
}

基本组件

按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class JButtonExample {
public static void main(String[] args) {
JFrame frame = new JFrame("按钮示例");
frame.setLayout(new FlowLayout());

// 创建按钮
JButton button1 = new JButton("普通按钮");
JButton button2 = new JButton("带图标按钮", new ImageIcon("icon.png"));

// 设置助记符 (Alt + M)
button1.setMnemonic(KeyEvent.VK_M);

// 设置工具提示
button1.setToolTipText("这是一个普通按钮");

// 添加事件监听
button1.addActionListener(e -> {
JOptionPane.showMessageDialog(frame, "按钮被点击了");
});

frame.add(button1);
frame.add(button2);
frame.pack();
frame.setVisible(true);
}
}

标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class JLabelExample {
public static void main(String[] args) {
JFrame frame = new JFrame("标签示例");
frame.setLayout(new FlowLayout());

// 文本标签
JLabel label1 = new JLabel("普通文本标签");

// 带图标的标签
Icon icon = new ImageIcon("icon.png");
JLabel label2 = new JLabel("带图标标签", icon, SwingConstants.LEFT);

// HTML 标签
JLabel label3 = new JLabel("<html><font color='red'>红色文本</font><br/>第二行</html>");

// 设置对齐方式
label1.setHorizontalAlignment(SwingConstants.CENTER);

frame.add(label1);
frame.add(label2);
frame.add(label3);
frame.pack();
frame.setVisible(true);
}
}

文本框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class JTextFieldExample {
public static void main(String[] args) {
JFrame frame = new JFrame("文本框示例");
frame.setLayout(new GridLayout(3, 2, 5, 5));

// 普通文本框
JTextField textField1 = new JTextField();
textField1.setText("默认文本");
textField1.setColumns(15);

// 带提示文本的文本框
JTextField textField2 = new JTextField();
textField2.setColumns(15);
setPlaceholder(textField2, "请输入用户名");

// 限制输入字符数
JTextField textField3 = new JTextField();
textField3.setColumns(15);
textField3.setDocument(new PlainDocument() {
@Override
public void insertString(int offs, String str, AttributeSet a)
throws BadLocationException {
if (getLength() + str.length() <= 10) {
super.insertString(offs, str, a);
}
}
});

frame.add(new JLabel("普通文本框:"));
frame.add(textField1);
frame.add(new JLabel("带提示文本:"));
frame.add(textField2);
frame.add(new JLabel("限制10字符:"));
frame.add(textField3);

frame.pack();
frame.setVisible(true);
}

private static void setPlaceholder(JTextField textField, String placeholder) {
textField.setText(placeholder);
textField.setForeground(Color.GRAY);

textField.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
if (textField.getText().equals(placeholder)) {
textField.setText("");
textField.setForeground(Color.BLACK);
}
}

@Override
public void focusLost(FocusEvent e) {
if (textField.getText().isEmpty()) {
textField.setText(placeholder);
textField.setForeground(Color.GRAY);
}
}
});
}
}

文本域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class JTextAreaExample {
public static void main(String[] args) {
JFrame frame = new JFrame("文本域示例");

JTextArea textArea = new JTextArea(10, 30);
textArea.setLineWrap(true); // 自动换行
textArea.setWrapStyleWord(true); // 单词边界换行

// 添加滚动条
JScrollPane scrollPane = new JScrollPane(textArea);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

frame.add(scrollPane);
frame.pack();
frame.setVisible(true);
}
}

复选框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class JCheckBoxExample {
public static void main(String[] args) {
JFrame frame = new JFrame("复选框示例");
frame.setLayout(new FlowLayout());

JCheckBox checkBox1 = new JCheckBox("选项1");
JCheckBox checkBox2 = new JCheckBox("选项2", true); // 默认选中
JCheckBox checkBox3 = new JCheckBox("选项3");

// 添加事件监听
checkBox1.addItemListener(e -> {
System.out.println("选项1: " + (e.getStateChange() == ItemEvent.SELECTED));
});

// 获取选中状态
JButton btn = new JButton("获取选中状态");
btn.addActionListener(e -> {
String selected = "选中的选项: ";
if (checkBox1.isSelected()) selected += "选项1 ";
if (checkBox2.isSelected()) selected += "选项2 ";
if (checkBox3.isSelected()) selected += "选项3 ";
JOptionPane.showMessageDialog(frame, selected);
});

frame.add(checkBox1);
frame.add(checkBox2);
frame.add(checkBox3);
frame.add(btn);

frame.pack();
frame.setVisible(true);
}
}

单选按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class JRadioButtonExample {
public static void main(String[] args) {
JFrame frame = new JFrame("单选按钮示例");
frame.setLayout(new FlowLayout());

// 创建单选按钮
JRadioButton radio1 = new JRadioButton("男");
JRadioButton radio2 = new JRadioButton("女");

// 创建按钮组确保只有一个被选中
ButtonGroup group = new ButtonGroup();
group.add(radio1);
group.add(radio2);

// 默认选中
radio1.setSelected(true);

// 添加事件监听
radio1.addActionListener(e -> System.out.println("性别: 男"));
radio2.addActionListener(e -> System.out.println("性别: 女"));

frame.add(radio1);
frame.add(radio2);
frame.pack();
frame.setVisible(true);
}
}

下拉列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class JComboBoxExample {
public static void main(String[] args) {
JFrame frame = new JFrame("下拉列表示例");
frame.setLayout(new FlowLayout());

// 创建下拉列表
String[] items = {"北京", "上海", "广州", "深圳"};
JComboBox<String> comboBox = new JComboBox<>(items);

// 设置为可编辑
comboBox.setEditable(true);

// 添加事件监听
comboBox.addActionListener(e -> {
String selected = (String) comboBox.getSelectedItem();
System.out.println("选中: " + selected);
});

frame.add(comboBox);
frame.pack();
frame.setVisible(true);
}
}

列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class JListExample {
public static void main(String[] args) {
JFrame frame = new JFrame("列表示例");

// 创建数据模型
DefaultListModel<String> model = new DefaultListModel<>();
model.addElement("苹果");
model.addElement("香蕉");
model.addElement("橙子");
model.addElement("葡萄");

// 创建列表
JList<String> list = new JList<>(model);
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);

// 添加滚动条
JScrollPane scrollPane = new JScrollPane(list);
scrollPane.setPreferredSize(new Dimension(200, 150));

// 添加事件监听
list.addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
List<String> selected = list.getSelectedValuesList();
System.out.println("选中: " + selected);
}
});

frame.add(scrollPane);
frame.pack();
frame.setVisible(true);
}
}

高级组件

表格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class JTableExample {
public static void main(String[] args) {
JFrame frame = new JFrame("表格示例");

// 数据
String[] columns = {"姓名", "年龄", "性别"};
Object[][] data = {
{"张三", 20, "男"},
{"李四", 25, "女"},
{"王五", 30, "男"}
};

// 创建表格
JTable table = new JTable(data, columns);

// 设置表格属性
table.setRowHeight(25);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

// 添加滚动条
JScrollPane scrollPane = new JScrollPane(table);
scrollPane.setPreferredSize(new Dimension(400, 200));

// 自定义渲染器可选
table.getColumnModel().getColumn(0).setCellRenderer(new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
if (row % 2 == 0) {
c.setBackground(new Color(240, 240, 240));
} else {
c.setBackground(Color.WHITE);
}
return c;
}
});

frame.add(scrollPane);
frame.pack();
frame.setVisible(true);
}
}

树结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class JTreeExample {
public static void main(String[] args) {
JFrame frame = new JFrame("树示例");

// 创建根节点
DefaultMutableTreeNode root = new DefaultMutableTreeNode("根目录");

// 创建子节点
DefaultMutableTreeNode node1 = new DefaultMutableTreeNode("文件夹1");
DefaultMutableTreeNode node2 = new DefaultMutableTreeNode("文件夹2");

// 添加子节点
node1.add(new DefaultMutableTreeNode("文件1.txt"));
node1.add(new DefaultMutableTreeNode("文件2.txt"));
node2.add(new DefaultMutableTreeNode("文件3.txt"));

root.add(node1);
root.add(node2);

// 创建树
JTree tree = new JTree(root);

// 添加滚动条
JScrollPane scrollPane = new JScrollPane(tree);
scrollPane.setPreferredSize(new Dimension(300, 200));

// 添加事件监听
tree.addTreeSelectionListener(e -> {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)
tree.getLastSelectedPathComponent();
if (node != null) {
System.out.println("选中: " + node.getUserObject());
}
});

frame.add(scrollPane);
frame.pack();
frame.setVisible(true);
}
}

布局管理

流式布局

1
2
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
// 参数对齐方式水平间距垂直间距

边界布局

1
2
3
4
5
6
JPanel panel = new JPanel(new BorderLayout(5, 5));
panel.add(new JButton("北"), BorderLayout.NORTH);
panel.add(new JButton("南"), BorderLayout.SOUTH);
panel.add(new JButton("东"), BorderLayout.EAST);
panel.add(new JButton("西"), BorderLayout.WEST);
panel.add(new JButton("中"), BorderLayout.CENTER);

网格布局

1
2
JPanel panel = new JPanel(new GridLayout(3, 2, 5, 5));
// 参数行数列数水平间距垂直间距

网格包布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class GridBagLayoutExample {
public static void main(String[] args) {
JFrame frame = new JFrame("GridBagLayout 示例");
frame.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();

// 设置约束
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(5, 5, 5, 5);

// 添加组件
gbc.gridx = 0;
gbc.gridy = 0;
frame.add(new JLabel("姓名:"), gbc);

gbc.gridx = 1;
gbc.gridy = 0;
gbc.gridwidth = 2;
frame.add(new JTextField(15), gbc);

gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridwidth = 1;
frame.add(new JLabel("年龄:"), gbc);

gbc.gridx = 1;
gbc.gridy = 1;
frame.add(new JTextField(5), gbc);

frame.pack();
frame.setVisible(true);
}
}

箱式布局

1
2
3
4
5
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(new JButton("按钮1"));
panel.add(Box.createVerticalStrut(10)); // 添加垂直间距
panel.add(new JButton("按钮2"));

事件处理

点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class EventExample {
public static void main(String[] args) {
JFrame frame = new JFrame("事件示例");
frame.setLayout(new FlowLayout());

JButton button = new JButton("点击");
JTextField textField = new JTextField(15);
JLabel label = new JLabel("等待操作...");

// ActionListener
button.addActionListener(e -> {
label.setText("按钮被点击了");
textField.setText("");
});

// MouseListener
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
button.setBackground(Color.LIGHT_GRAY);
}

@Override
public void mouseExited(MouseEvent e) {
button.setBackground(null);
}
});

// KeyListener
textField.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
label.setText("输入: " + textField.getText());
}
});

frame.add(button);
frame.add(textField);
frame.add(label);
frame.pack();
frame.setVisible(true);
}
}

单窗口关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SingleWindowApp {
public static void main(String[] args) {
JFrame frame = new JFrame("单窗口应用");

// 直接使用 EXIT_ON_CLOSE
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

// 或者使用 DISPOSE_ON_CLOSE 并在关闭后退出
// frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// frame.addWindowListener(new WindowAdapter() {
// @Override
// public void windowClosed(WindowEvent e) {
// System.exit(0);
// }
// });

frame.setSize(400, 300);
frame.setVisible(true);
}
}

多窗口关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class MultiWindowApp {
private static int openWindows = 0;

public static void main(String[] args) {
// 主窗口
createMainWindow();

// 可以创建子窗口
JButton newWindowBtn = new JButton("打开新窗口");
// ... 添加按钮事件
}

private static void createMainWindow() {
JFrame mainWindow = new JFrame("主窗口");
openWindows++;

mainWindow.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
mainWindow.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if (openWindows == 1) {
// 最后一个窗口询问是否退出
int result = JOptionPane.showConfirmDialog(
mainWindow,
"是否退出程序",
"退出确认",
JOptionPane.YES_NO_OPTION
);
if (result == JOptionPane.YES_OPTION) {
System.exit(0);
}
} else {
// 不是最后一个窗口只关闭当前窗口
openWindows--;
mainWindow.dispose();
}
}
});

mainWindow.setSize(400, 300);
mainWindow.setVisible(true);
}
}

带线程窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class AppWithBackgroundThread {
private static volatile boolean running = true;
private static Thread backgroundThread;

public static void main(String[] args) {
// 启动后台线程
startBackgroundThread();

JFrame frame = new JFrame("后台任务应用");

// 关闭时正确停止后台线程
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
int option = JOptionPane.showConfirmDialog(
frame,
"后台任务正在运行确定要退出吗",
"确认退出",
JOptionPane.YES_NO_OPTION
);

if (option == JOptionPane.YES_OPTION) {
// 停止后台线程
running = false;
try {
backgroundThread.join(3000); // 等待3秒
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
System.exit(0);
}
}
});

frame.setSize(400, 300);
frame.setVisible(true);
}

private static void startBackgroundThread() {
backgroundThread = new Thread(() -> {
while (running) {
try {
System.out.println("后台任务执行中...");
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
System.out.println("后台线程已停止");
});
backgroundThread.setDaemon(false); // 设置为非守护线程
backgroundThread.start();
}
}

高级特性

自定义外观

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class LookAndFeelExample {
public static void main(String[] args) {
try {
// 设置为系统外观
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());

// 设置为跨平台外观Metal
// UIManager.setLookAndFeel(
// UIManager.getCrossPlatformLookAndFeelClassName());

// 设置为 Nimbus 外观
// UIManager.setLookAndFeel(
// "javax.swing.plaf.nimbus.NimbusLookAndFeel");

} catch (Exception e) {
e.printStackTrace();
}

// 创建 GUI
JFrame frame = new JFrame("外观示例");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
frame.setVisible(true);
}
}

定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class TimerExample {
public static void main(String[] args) {
JFrame frame = new JFrame("定时器示例");
frame.setLayout(new FlowLayout());

JLabel label = new JLabel("0");
label.setFont(new Font("Arial", Font.BOLD, 24));

// 创建定时器每1000毫秒触发一次
Timer timer = new Timer(1000, e -> {
int value = Integer.parseInt(label.getText());
label.setText(String.valueOf(value + 1));
});

JButton startBtn = new JButton("开始");
JButton stopBtn = new JButton("停止");

startBtn.addActionListener(e -> timer.start());
stopBtn.addActionListener(e -> timer.stop());

frame.add(label);
frame.add(startBtn);
frame.add(stopBtn);
frame.pack();
frame.setVisible(true);
}
}

多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class SwingThreadExample {
public static void main(String[] args) {
// 在事件调度线程中创建 GUI
SwingUtilities.invokeLater(() -> {
createAndShowGUI();
});
}

private static void createAndShowGUI() {
JFrame frame = new JFrame("多线程示例");
frame.setLayout(new FlowLayout());

JLabel label = new JLabel("等待...");
JButton button = new JButton("开始任务");

button.addActionListener(e -> {
// 在后台线程执行耗时操作
new Thread(() -> {
try {
// 模拟耗时操作
Thread.sleep(3000);

// 更新 UI必须在事件调度线程中
SwingUtilities.invokeLater(() -> {
label.setText("任务完成");
});
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}).start();

label.setText("任务执行中...");
});

frame.add(label);
frame.add(button);
frame.pack();
frame.setVisible(true);
}
}

常见问题

线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 正确的 Swing 编程方式
public class BestPractice {
public static void main(String[] args) {
// 所有 GUI 创建必须在 EDT 中
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
// ... 创建 GUI
frame.setVisible(true);
});
}

// 更新 GUI 组件
private void updateLabel(JLabel label, String text) {
if (SwingUtilities.isEventDispatchThread()) {
label.setText(text);
} else {
SwingUtilities.invokeLater(() -> label.setText(text));
}
}
}

资源管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 确保资源正确释放
public class ResourceManagement {
public static void main(String[] args) {
JFrame frame = new JFrame();

// 添加窗口监听器释放资源
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
// 清理资源
cleanup();
System.exit(0);
}
});

// 使用 try-with-resources 管理资源
try (InputStream is = new FileInputStream("file.txt")) {
// 处理文件
} catch (IOException e) {
e.printStackTrace();
}
}

private static void cleanup() {
// 关闭数据库连接文件流等
}
}

性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class PerformanceOptimization {

// 避免频繁创建对象
private static final Color HIGHLIGHT_COLOR = new Color(200, 200, 255);

// 使用双缓冲减少闪烁
public class CustomPanel extends JPanel {
public CustomPanel() {
setDoubleBuffered(true);
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// 自定义绘制代码
}
}

// 延迟加载组件
private JComponent createExpensiveComponent() {
if (expensiveComponent == null) {
expensiveComponent = new ExpensiveComponent();
}
return expensiveComponent;
}
}

界面卡顿

1
2
3
4
5
6
7
8
9
10
11
12
13
// 问题在主线程执行耗时操作
button.addActionListener(e -> {
// 错误会阻塞 EDT
longRunningOperation();
});

// 解决方案使用后台线程
button.addActionListener(e -> {
new Thread(() -> {
longRunningOperation();
SwingUtilities.invokeLater(() -> updateUI());
}).start();
});

组件刷新

1
2
3
4
5
// 问题组件不自动刷新
panel.add(newButton);
// 需要调用 revalidate 和 repaint
panel.revalidate();
panel.repaint();

内存泄漏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 问题监听器未移除导致内存泄漏
class MyFrame extends JFrame {
private Timer timer;

public MyFrame() {
timer = new Timer(1000, e -> updateTime());
timer.start();
}

@Override
public void dispose() {
// 清理资源
if (timer != null) {
timer.stop();
timer = null;
}
super.dispose();
}
}

学习资源

  • 视频
    • 韩顺平零基础 30 天学会 Javahttps://www.bilibili.com/video/BV1fh411y7R8
    • 宋红康全网最全Java零基础入门教程https://www.bilibili.com/video/BV1Kb411W75N
    • JDK8 新特性详解https://www.bilibili.com/video/BV1k64y1R7sA
    • Java 核心技术卷https://www.bilibili.com/video/BV18u411q7jv
    • 尚硅谷大厂必备技术之JUC并发编程2021最新版https://www.bilibili.com/video/BV1Kw411Z7dF
    • 黑马程序员全面深入学习Java并发编程https://www.bilibili.com/video/BV16J411h7Rd
  • 文档
    • 菜鸟教程 Javahttps://www.runoob.com/java/java-tutorial.html
    • 菜鸟教程 Java 8https://www.runoob.com/java/java8-new-features.html
    • 廖雪峰 Java 教程https://www.liaoxuefeng.com/wiki/1252599548343744
    • Java API 中文文档https://www.matools.com/api/java8
    • Java 并发知识点总结https://github.com/CL0610/Java-concurrency
  • 书籍
    • Java 核心技术卷 Ihttps://pan.baidu.com/s/1G36MF1XrLhKMaBpNcfM61g?pwd=ep11
  • 工具
    • 在线编写运行https://c.runoob.com/compile/10/
  • 游戏
    • Codegymhttps://codegym.cc/zh
  • 练手项目
    • 牛客题库https://www.nowcoder.com/intelligentTest
    • Java 实现简单计算器https://www.lanqiao.cn/courses/185
    • Eclipse 实现 Java 编辑器https://www.lanqiao.cn/courses/287
    • 一本糊涂账https://how2j.cn/module/104.html
    • Java 五子棋https://blog.csdn.net/cnlht/article/details/8176130
    • Java 中国象棋https://blog.csdn.net/cnlht/article/details/8205733
    • JAVA GUI 图书馆管理系统https://github.com/uboger/LibraryManager
    • JAVA 坦克大战小游戏https://github.com/wangzhengyi/TankWar
    • Swing 编写的俄罗斯方块https://github.com/HelloClyde/Tetris-Swing
    • 小小记账本https://github.com/xenv/SmallAccount