Java(四):4.0 [核心] Java I/O 流体系与实战

Java(四):4.0 [核心] Java I/O 流体系与实战
Prorise4.0 [核心] Java I/O 流体系与实战
本章将深入Java的I/O(输入/输出)世界。I/O是程序与外部世界(如文件、网络、控制台)沟通的桥梁。我们将从I/O的“四大家族”和装饰器设计模式入手,理解其核心设计思想,然后深入文件操作的现代实践(NIO.2),并最终探讨如何在真实项目中,利用强大的第三方库来告别繁琐的I/O样板代码。
4.1 I/O 核心概念与设计模式
4.1.1 面试题引入
“请谈谈你对Java I/O的理解。字节流和字符流有什么区别?节点流和处理流呢?”
4.1.2 流的“四大家族”与核心区别
I/O的本质是程序与外部数据源之间的数据传输通道。Java通过流(Stream)这一抽象概念来表示这个通道,并根据数据传输单位和方向的不同,提供了“四大家族”作为所有I/O操作的基石:
分类维度 | 方向 | 字节流 (处理一切数据,如图片、视频、文本) | 字符流 (专为处理文本数据优化) |
---|---|---|---|
输入(读) | 数据源 -> 程序 | InputStream (抽象基类) | Reader (抽象基类) |
输出(写) | 程序 -> 数据源 | OutputStream (抽象基类) | Writer (抽象基类) |
字节流 vs. 字符流
这是I/O体系中最根本的区别,也是面试中的高频考点。
字节流 (
InputStream
/OutputStream
) 以字节(byte,
8-bit)为单位进行读写。它是最原始、最通用的流,可以处理任何类型的二进制数据,如图片、音频、视频文件等。但它的缺点在于,在处理文本时,它不关心字符编码。字符流 (
Reader
/Writer
) 以字符(char,
16-bit)为单位进行读写。它是在字节流的基础上构建的,专门用于处理文本数据。其内部封装了字节到字符的解码和字符到字节的编码过程。因此,字符流能够正确地处理包含各种语言(如中文)的文本,有效避免乱码问题。
代码示例:乱码问题的产生与解决
场景:我们有一个UTF-8编码的文本文件
test.txt
,内容为“你好Java”。
错误演示:使用字节流读取文本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package com.example;
import java.io.FileInputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("test.txt");
System.out.println("--- 使用字节流逐字节读取 ---");
int byteData;
while ((byteData = fis.read()) != -1) {
System.out.print((char) byteData);
}
System.out.println("\n结果:出现了乱码。");
}
}原因分析:在UTF-8编码中,一个中文字符通常由3个字节表示。上述代码一次只读一个字节,并试图将其强转为字符,自然无法正确还原“你”和“好”这两个字,导致乱码。
正确方式:使用字符流读取文本
1
2
3
4package com.example;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
FileReader reader = new FileReader("test.txt");
System.out.println("--- 使用字符流逐字符读取 ---");
int byteData;
while ((byteData = reader.read()) != -1) {
System.out.print((char) byteData);
}
System.out.println("\n结果:乱码没了");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#### **4.1.3 节点流 vs. 处理流**
这是从功能层面对流的另一种划分方式。
- **节点流 (Node
Stream)**:也被称为“低级流”。它们是直接与数据源(如文件、网络套接字、内存数组)相连接的“管道”,负责实际的数据传输。例如
`FileInputStream`、`ByteArrayInputStream`。
- **处理流 (Processing
Stream)**:也被称为“高级流”。它们**不直接连接数据源**,而是“套”在已存在的流(节点流或其他处理流)之上,像一个“过滤器”或“增强器”,为原始的流增加额外的功能。例如
`BufferedInputStream`(增加缓冲功能)、`ObjectInputStream`(增加对象反序列化功能)。
* **缓冲功能**:`BufferedInputStream`
通过内部缓存机制,一次性读取较多数据,减少与底层数据源(如文件、网络)的交互次数,从而提高读取性能。
* **反序列化功能**:`ObjectInputStream` 可以将之前通过 `ObjectOutputStream`
写入的 Java 对象恢复为内存中的对象,实现对象的持久化和传输。
为了最清晰地展示两者的区别与关系,我们设定一个共同的、非常常见的开发任务:**读取一个文本文件的内容,并将其逐行打印到控制台**。
假设我们有一个名为 `poem.txt` 的文件,内容如下:
床前明月光,疑是地上霜。举头望明月,低头思故乡。
1 |
|
分析这种方式的痛点:
- 操作底层:我们直接和原始的
byte[]
打交道。 - 功能有限:
FileInputStream
本身不提供按行读取 (readLine
)
这样的便捷功能。 - 编码繁琐:需要手动处理字节到字符串的转换。
场景二:使用处理流包装节点流(高级、推荐的方式)
现在,我们引入处理流 BufferedReader
来“装饰”或“增强”节点流 FileReader
。
1 | package com.example; |
两种方式对比总结
对比维度 | 仅使用节点流 (FileInputStream ) | 处理流 + 节点流 (BufferedReader + FileReader ) |
---|---|---|
职责 | 负责连接数据源,进行最基础的字节读写。 | 节点流负责连接数据源,处理流负责功能增强。 |
易用性 | 差,需要手动处理缓冲、编码、按行读取等。 | 高,提供了readLine() 等便捷API。 |
性能 | 低,每次read() 都可能是一次物理磁盘I/O。 | 高,内部缓冲机制大大减少了物理I/O次数。 |
代码量 | 繁琐,样板代码多。 | 简洁,代码意图清晰。 |
通过这个对比,我们可以清晰地看到处理流的价值:它将开发者从复杂的底层I/O细节中解放出来,让我们能更专注于业务逻辑本身,同时还能获得更好的性能。
在实际开发中,我们几乎总是使用处理流包装节点流的方式来进行I/O操作,这正是装饰器模式在Java
I/O中应用的精髓。
4.1.4 [设计模式] java.io
的灵魂:装饰器模式
理解Java I/O体系的关键,在于理解其背后优美的装饰器设计模式(Decorator
Pattern)。该模式允许我们向一个现有对象动态地添加新的功能,同时又不改变其结构。
在I/O中,FileInputStream
等节点流是我们的基础组件(ConcreteComponent),而BufferedInputStream
等处理流则是装饰器(Decorator)。我们可以像搭积木一样,自由地将这些装饰器“套”在基础组件上,按需组合出强大的功能。
代码示例:装饰器的层层嵌套
场景:从一个文件中,以高效缓冲的方式,读取一个被序列化后的Java对象。
1 | package com.example; |
这种设计使得Java
I/O体系既灵活又可扩展。当需要新功能时,只需创建一个新的处理流(装饰器)即可,而无需修改现有的任何流类。
4.2 文件操作:从 传统File
到 现代NIO.2
在理解了I/O流的分类和装饰器设计模式后,我们将聚焦于I/O操作的一个核心应用——文件操作。这包括如何创建、删除、重命名文件和目录,以及如何读取它们的属性。Java为此提供了两代API,我们将对比学习,并重点掌握现代化的解决方案。
4.2.1 传统方式:java.io.File
类
File
类是Java早期用于表示文件系统中的一个文件或目录路径的抽象。它可以用于执行创建、删除、重命名等操作,但其设计存在一些固有缺陷,因此在现代Java开发中已不被推荐作为首选。
核心用途与场景
在维护旧项目或使用一些尚未升级到NIO.2的老旧第三方库时,我们仍然会遇到File
类。
常用方法速查表
方法签名 | 功能描述 |
---|---|
boolean exists() | 检查文件或目录是否存在。 |
boolean createNewFile() | 创建一个新文件。 |
boolean mkdir() / mkdirs() | 创建单级/多级目录。 |
boolean delete() | 删除文件或空目录。 |
boolean renameTo(File dest) | 重命名或移动文件。 |
String getName() / getAbsolutePath() | 获取名称/绝对路径。 |
long length() | 获取文件大小(字节)。 |
boolean isDirectory() / isFile() | 判断是目录还是文件。 |
File[] listFiles() | 列出目录下的文件和子目录。 |
设计缺陷与痛点
- 错误处理不友好:许多关键操作(如
delete()
,renameTo()
)在失败时仅返回false
,而不会抛出异常。这使得我们无法得知失败的具体原因(是权限不足?文件被占用?还是其他问题?),给可靠的错误处理带来了巨大困难。 - 功能有限:原生不支持符号链接等现代文件系统特性,也无法方便地访问和修改文件元数据(如权限、所有者等)。
- 无力处理非空目录:
delete()
方法只能删除文件或空目录,要删除整个目录树需要自己编写复杂的递归逻辑。
4.2.2 [现代实践] java.nio.file
包 (“NIO.2”)
自Java
7起,NIO.2的引入为文件系统操作带来了革命性的变化。它以Path
接口为核心,通过Paths
和Files
两个强大的工具类,提供了功能更全面、设计更优良、错误处理更明确的现代文件操作API。
- 核心优势:
- 明确的异常处理:所有操作在失败时都会抛出具体的
IOException
,让问题无处遁形。 - 强大的功能:原生支持符号链接、文件属性视图、文件系统监视等高级功能。
- 高效的API:提供了大量便捷、高效的静态方法,可以用一行代码完成过去需要一个方法块才能实现的功能。
- 明确的异常处理:所有操作在失败时都会抛出具体的
代码实战:Files
工具类的强大功能
下面通过几个核心场景,对比展示NIO.2相比于传统File
类的巨大优势。
场景一:文件的创建与读写
需求:创建一个文本文件,向其中写入内容,然后再读取出来。
1 | package com.example; |
场景二:文件与目录的复制
需求:将一个文件复制到另一个位置,并将一个完整的目录(包含子目录和文件)复制到新位置。
1 | package com.example; |
结论:在所有新的Java项目中,都应优先并坚持使用java.nio.file
包进行文件和目录操作。它更安全、功能更强大、代码也更现代化。
4.2.3 java.nio.file.Files
核心方法速查表
好的,明白了。为了让速查表更加简洁、一目了然,我将移除“方法签名”列中的返回类型和修饰符,只保留核心的方法名和参数示意。
1. 文件和目录检查
方法名 | 功能描述 | 注意事项 / 最佳实践 |
---|---|---|
exists(...) | 检查文件或目录是否存在。 | 最常用的检查方法。 |
notExists(...) | 检查文件或目录是否不存在。 | !Files.exists(path) 的一个更具可读性的替代方案。 |
isDirectory(...) | 判断路径是否为目录。 | |
isRegularFile(...) | 判断路径是否为普通文件。 | |
isReadable(...) | 判断文件是否可读。 | |
isWritable(...) | 判断文件是否可写。 | |
isExecutable(...) | 判断文件是否可执行。 | |
isSameFile(...) | 判断两个Path 是否指向同一个文件。 | 比p1.equals(p2) 更可靠,因为它会处理符号链接等情况。 |
2. 文件和目录创建
方法名 | 功能描述 | 注意事项 / 最佳实践 |
---|---|---|
createFile(...) | 创建一个新文件。 | 如果文件已存在,会抛出FileAlreadyExistsException 。 |
createDirectory(...) | 创建一个新目录。 | 只能创建单级目录,如果父目录不存在会抛出异常。 |
createDirectories(...) | 强烈推荐。创建多级目录。 | 如果父目录不存在,会自动一并创建,非常方便。 |
createTempFile(...) | 在默认或指定位置创建一个临时文件。 | 常用于需要临时存储数据的场景。 |
createTempDirectory(...) | 创建一个临时目录。 |
3. 文件和目录删除
方法名 | 功能描述 | 注意事项 / 最佳实践 |
---|---|---|
delete(...) | 删除一个文件或空目录。 | 如果路径不存在,抛出NoSuchFileException 。如果目录非空,抛出DirectoryNotEmptyException 。 |
deleteIfExists(...) | 如果文件或目录存在,则删除它。 | 推荐使用。比delete() 更安全,因为它不会在文件不存在时抛出异常,只会返回false 。 |
4. 文件读/写操作(小文件)
这些方法会将文件的全部内容一次性读入内存,非常便捷,但只适用于小文件。
方法名 | 功能描述 | 注意事项 / 最佳实践 |
---|---|---|
readAllBytes(...) | 将文件的所有内容读取为一个字节数组。 | 注意内存溢出(OOM)风险,不适用于大文件。 |
readAllLines(...) | 将文件的所有行读取到一个字符串列表中。 | 注意内存溢出风险。默认使用UTF-8编码。 |
write(...) | 将一个字节数组或字符串集合写入文件。 | 默认会覆盖已有文件。可使用StandardOpenOption 指定追加、创建等。 |
writeString(...) | [Java 11+] 将字符串写入文件。 | 写入文本最简单的方式。 |
readString(...) | [Java 11+] 读取文件内容为字符串。 | 读取小文本文件最简单的方式。 |
5. 文件读/写操作(大文件/流式处理)
当处理大文件时,应使用流式API,避免一次性将所有内容加载到内存。
方法名 | 功能描述 | 注意事项 / 最佳实践 |
---|---|---|
newInputStream(...) | 打开一个文件,返回一个用于读取的InputStream 。 | 处理大文件的标准方式。获取流之后,需配合try-with-resources 使用。 |
newOutputStream(...) | 打开或创建一个文件,返回一个用于写入的OutputStream 。 | 同上。 |
newBufferedReader(...) | 打开一个文件,返回一个用于读取文本的BufferedReader 。 | 提供了高效的readLine() 方法,是读取大文本文件的首选。 |
newBufferedWriter(...) | 打开或创建一个文件,返回一个BufferedWriter 。 | 写入大文本文件的首选。 |
lines(...) | [Java 8+] 返回一个由文件所有行组成的Stream 。 | 懒加载,非常适合用函数式编程风格处理大文本文件,内存占用极小。 |
6. 复制与移动
方法名 | 功能描述 | 注意事项 / 最佳实践 |
---|---|---|
copy(...) | 复制一个文件或目录。 | 默认情况下,如果目标文件已存在会失败。需使用StandardCopyOption.REPLACE_EXISTING 来覆盖。复制目录时,只复制目录本身,不复制其内容。 |
move(...) | 移动或重命名一个文件。 | 可以指定StandardCopyOption.ATOMIC_MOVE 来保证操作的原子性。 |
7. 目录遍历 (Stream API)
方法名 | 功能描述 | 注意事项 / 最佳实践 |
---|---|---|
list(...) | [Java 8+] 返回一个表示目录下所有条目(不含子目录)的Stream 。 | 非递归,只遍历当前层级。 |
walk(...) | [Java 8+] 返回一个从指定路径开始、递归遍历所有文件和目录的Stream 。 | 功能强大,可以配合filter 等操作轻松实现文件查找等功能。 |
find(...) | [Java 8+] 功能类似walk ,但可以额外传入一个匹配器来筛选路径。 | 比walk 后接filter 更高效。 |
4.3 核心I/O处理流搭配使用
在掌握了文件和目录的表示方法后,我们回到流本身,聚焦于如何通过组合不同的处理流,来高效、灵活地读写文件内容。
4.3.1 缓冲流 (BufferedInputStream
/ BufferedReader
): 性能优化的基石
面试题引入
“为什么我们在进行文件IO时,总是推荐使用缓冲流?它的原理是什么?”
核心原理解析
计算机中,对磁盘或网络的I/O操作(系统调用)相比于内存操作,是极其缓慢的。如果我们直接使用FileInputStream
的read()
方法逐字节读取文件,那么每读取一个字节,就可能触发一次昂贵的物理磁盘访问。
缓冲流(Buffered
Stream)正是为解决这一性能瓶颈而生。它的原理是在内部维护一个内存缓冲区(一个字节或字符数组,默认大小通常为8192)。
- 读取时:
BufferedInputStream
会一次性从磁盘读取一大块数据(例如8KB)填充到内部缓冲区。之后你再调用read()
方法时,它会直接从高速的内存缓冲区中返回数据,直到缓冲区耗尽,才会再次触发下一次对磁盘的大块读取。 - 写入时:
BufferedOutputStream
会先将你写入的数据存放在缓冲区,直到缓冲区满了,或者你手动调用flush()
方法,它才会将整个缓冲区的数据一次性写入磁盘。
结论:缓冲流通过**“化零为整”**的策略,用一次大的I/O操作替代了无数次小的I/O操作,极大地减少了与底层物理设备的交互次数,从而实现性能的飞跃。
代码实战:文件复制性能对比
场景:复制一个文件,对比使用缓冲流和不使用缓冲流的耗时。
1 | package com.example; |
运行结果(示例):
1 | 不使用缓冲流耗时: 12629 ms |
可以看到,性能差异是数量级的。因此,在进行任何文件I/O时,使用缓冲流包装节点流都应成为一种编程习惯。
4.3.2 转换流 (InputStreamReader
/ OutputStreamWriter
): 字节与字符的桥梁
核心用途
转换流的核心作用是适配器,它能将字节流转换为字符流,并在转换过程中处理字符编码。
应用场景
当你需要读写的文本文件编码与当前操作系统的默认编码不一致时,必须使用转换流来显式指定正确的编码,否则就会产生乱码。
- 读取:例如,在UTF-8的服务器上读取一个由Windows记事本(默认GBK编码)生成的中文文件。
- 写入:例如,无论程序运行在什么系统上,都希望统一生成UTF-8编码的配置文件。
代码示例:读取GBK文件并转存为UTF-8
1 | package com.example; |
4.3.3 对象流 (ObjectInputStream
/ ObjectOutputStream
): 对象的序列化与反序列化
面试题引入
“什么是Java的序列化?
transient
关键字和serialVersionUID
有什么作用?”
核心概念
- 序列化 (Serialization):将一个Java对象的状态转换为字节序列的过程。
- 反序列化 (Deserialization):从字节序列中恢复并重建Java对象的过程。
- 用途:实现对象的持久化(保存到文件或数据库)和网络传输。
关键知识点
Serializable
接口:一个类必须实现这个标记接口(没有任何方法),其对象才能被序列化。它像一个“通行证”,告诉JVM这个类的对象可以被“扁平化”为字节。transient
关键字:用于修饰字段。被transient
修饰的字段将被排除在序列化过程之外,不会被写入字节流。反序列化后,该字段的值会是其类型的默认值(如对象为null
,int
为0)。常用于密码、安全令牌、缓存数据等敏感或无需持久化的字段。serialVersionUID
(面试核心):这是一个用于标识可序列化类版本的long
型常量。- 作用:在反序列化时,JVM会比较字节流中的
serialVersionUID
和当前加载的类中的serialVersionUID
。如果两者不一致,会抛出InvalidClassException
,以防止因类版本不兼容导致的数据错乱。 - 最佳实践:强烈建议所有可序列化类都显式声明一个
private static final long serialVersionUID
。如果不声明,编译器会自动生成一个,但这个自动生成的值对类的结构非常敏感(如增删字段),稍有改动就会变化,导致旧的序列化数据无法被新版类反序列化。
- 作用:在反序列化时,JVM会比较字节流中的
代码实战:将User对象持久化到文件
1 | package com.example; |
4.4 [实战进阶] 简化I/O操作的第三方库
4.4.1 面试题引入与核心思想
面试题引入
“在实际项目中,你还会频繁地手动编写
try-with-resources
和循环来复制一个文件吗?有没有更便捷、更专业的处理方式?”
核心思想:为什么需要第三方库?
虽然我们必须深入理解JDK原生I/O流的体系,因为它是所有I/O操作的基础,也是排查底层问题的关键。但在真实的、快节奏的商业项目开发中,对于那些反复出现的通用I/O任务(如读写文件、复制目录等),如果每次都手动编写原始的流处理代码,会存在几个明显的问题:
- 代码冗长:完成一个简单的任务需要创建多个流对象、编写循环、处理异常,代码显得非常臃肿。
- 容易出错:手动管理资源和编写逻辑,稍有不慎就可能导致资源未关闭、缓冲区处理不当等难以察觉的BUG。
- 效率低下:重复编写同样功能的代码,是对开发时间的浪费。
因此,在专业开发领域,我们遵循“不重复造轮子 (Don’t Reinvent the
Wheel)”的原则。对于I/O操作,社区已经为我们提供了极其优秀、经过数万个项目验证的“轮子”——第三方工具库。
4.4.2 主流库介绍:Apache Commons IO
Apache Commons IO
是Java生态中处理I/O的事实上的行业标准。它是一个稳定、可靠、功能极其丰富的工具包,几乎是所有Java项目的必备依赖之一。它的核心价值在于,将那些繁琐的I/O样板代码封装成了简单、强大的一行代码。
- 如何引入: 在Maven项目中,只需在
pom.xml
中添加以下依赖即可:1
2
3
4
5<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.14.0</version>
</dependency> - 核心工具类:
FileUtils
:面向文件(File
对象)和目录的操作。IOUtils
:面向流(InputStream
,OutputStream
等)的操作。
4.4.3 代码实战:JDK原生写法 vs. Commons IO
一行代码
下面,我们将通过几个鲜明的“之前 vs. 之后”的对比,来感受Commons IO
的威力。
场景一:读取整个文件到字符串
- JDK 原生写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.example;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Main {
public static String readFileWithJDK(String filePath) throws IOException {
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append(System.lineSeparator());
}
}
return content.toString();
}
// ... main方法调用 ...
} - Commons IO 写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.example;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class Main {
public static String readFileWithCommonsIO(String filePath) throws IOException {
// 一行代码,搞定!内部已处理好所有流的打开和关闭。
return FileUtils.readFileToString(new File(filePath), StandardCharsets.UTF_8);
}
// ... main方法调用 ...
}
场景二:复制文件
- JDK 原生写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package com.example;
import java.io.*;
public class Main {
public static void copyFileWithJDK(String src, String dest) throws IOException {
try (InputStream in = new BufferedInputStream(new FileInputStream(src));
OutputStream out = new BufferedOutputStream(new FileOutputStream(dest))) {
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
}
}
// ... main方法调用 ...
} - Commons IO 写法
1
2
3
4
5
6
7
8
9
10
11
12
13package com.example;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
public class Main {
public static void copyFileWithCommonsIO(String src, String dest) throws IOException {
// 同样是一行代码
FileUtils.copyFile(new File(src), new File(dest));
}
// ... main方法调用 ...
}
场景三:递归删除目录
JDK 原生写法
JDK的File.delete()
无法删除非空目录。要实现此功能,必须自己编写一个递归方法,先删除目录下的所有文件和子目录,最后再删除该目录本身,过程复杂且容易出错。Commons IO 写法
1
package com.example;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
public class Main {
public static void deleteDirWithCommonsIO(String dirPath) throws IOException {
// 无论目录是否为空,一行代码安全删除
FileUtils.deleteDirectory(new File(dirPath));
}
// ... main方法调用 ...
}
```
4.5 Apache Commons IO
核心方法速查表
FileUtils
类:面向文件和目录的操作
1. 读操作 (Read Operations)
方法名 | 功能描述 |
---|---|
String readFileToString(File file, Charset cs) | **(最常用)**将整个文件内容读取为一个字符串。 |
List<String> readLines(File file, Charset cs) | 将文件的每一行读取到一个字符串列表 (List<String> ) 中。 |
byte[] readFileToByteArray(File file) | 将整个文件内容读取为一个字节数组 (byte[] )。 |
2. 写操作 (Write Operations)
方法名 | 功能描述 |
---|---|
void writeStringToFile(File file, String data, ...) | **(最常用)**将一个字符串写入文件(会覆盖或追加)。 |
void writeLines(File file, Collection<?> lines, ...) | 将一个字符串集合(如List )逐行写入文件。 |
void writeByteArrayToFile(File file, byte[] data) | 将一个字节数组写入文件。 |
3. 复制与移动 (Copy & Move Operations)
方法名 | 功能描述 |
---|---|
void copyFile(File src, File dest) | **(常用)**复制一个文件到新位置。 |
void copyDirectory(File src, File dest) | **(强大)**递归复制整个目录及其所有内容。 |
void copyFileToDirectory(File src, File destDir) | 将一个文件复制到指定的目录下。 |
void moveFile(File src, File dest) | 移动一个文件(本质上是“复制后删除”)。 |
void moveDirectory(File src, File dest) | 移动整个目录。 |
void moveToDirectory(File src, File destDir, ...) | 将文件或目录移动到指定的目录下。 |
4. 删除与清空 (Delete & Clean Operations)
方法名 | 功能描述 |
---|---|
void deleteDirectory(File dir) | **(强大)**递归删除整个目录,无论其是否为空。 |
void cleanDirectory(File dir) | 清空一个目录下的所有内容,但不删除目录本身。 |
boolean forceDelete(File file) | 强制删除一个文件或目录。如果删除失败,会尝试多次。 |
5. 状态检查与其它
方法名 | 功能描述 |
---|---|
long sizeOf(File file) | 获取一个文件的大小。 |
long sizeOfDirectory(File dir) | **(常用)**递归计算整个目录的大小。 |
boolean isFileNewer(File file, ...) | 判断一个文件是否比另一个文件或指定时间戳更新。 |
IOUtils
类:面向流的操作
1. 读/转换操作 (Read / Convert Operations)
方法名 | 功能描述 |
---|---|
String toString(InputStream in, Charset cs) | **(最常用)**将一个InputStream 或Reader 的内容读取为一个字符串。 |
byte[] toByteArray(InputStream in) | **(常用)**将一个InputStream 或Reader 的内容读取为一个字节数组。 |
InputStream toInputStream(String input, Charset cs) | 将一个CharSequence (如String )转换为一个InputStream 。 |
List<String> readLines(InputStream in, Charset cs) | 将一个InputStream 或Reader 的内容按行读取到一个List<String> 中。 |
2. 写操作 (Write Operations)
方法名 | 功能描述 |
---|---|
void write(String data, OutputStream out, ...) | 将一个String 或byte[] 的内容写入到一个OutputStream 或Writer 中。 |
3. 复制操作 (Copy Operations)
方法名 | 功能描述 |
---|---|
int copy(InputStream in, OutputStream out) | **(最常用)**将一个InputStream 的内容复制到一个OutputStream 中,或将Reader 复制到Writer 。返回复制的字节/字符数。 |
long copyLarge(InputStream in, OutputStream out) | 用于复制大于2GB的超大流。 |
4. 关闭操作 (Close Operations)
方法名 | 功能描述 |
---|---|
void closeQuietly(Closeable c) | (历史著名)安静地关闭一个Closeable (如流),忽略所有异常。注意:在Java 7及之后,官方推荐使用try-with-resources 语句来自动管理资源,此方法的必要性已大大降低,被视为一种过时的模式。 |