Java:File类、递归、字符集、IO流体系及Commons-io框架
1. File类:文件与目录的操作入口
1.1 File类的作用与创建对象
File类是Java操作文件和目录的基础,它仅表示文件/目录的路径信息(不直接操作文件内容),通过路径定位文件系统中的资源。
创建File对象:绝对路径与相对路径
- 绝对路径:从盘符开始的完整路径,如
C:/Users/笔记.txt(Windows)或/home/user/笔记.txt(Linux/Mac),定位唯一且固定。 - 相对路径:从程序运行目录(当前工作目录)出发的路径,如
./src/main/java(./表示当前目录),移植性强(项目移动时无需修改路径)。
代码案例:
import java.io.File;
public class FileDemo {
public static void main(String[] args) {
// 1. 绝对路径创建File对象(Windows系统示例)
File absoluteFile = new File("C:/Users/Desktop/学习计划.txt");
System.out.println("绝对路径是否存在:" + absoluteFile.exists()); // 判断文件是否存在
// 2. 相对路径创建File对象(假设当前工作目录是项目根目录)
// ./ 表示当前目录,可省略;src是项目下的源代码目录
File relativeFile = new File("src/main/resources/data.txt");
System.out.println("相对路径文件名称:" + relativeFile.getName()); // 获取文件名
}
}
1.2 操作文件和目录的常用方法
| 方法名 | 作用 | 示例场景 |
|---|---|---|
createNewFile() | 创建新文件(若不存在) | 创建日志文件 |
mkdir() | 创建单级目录 | 创建"temp"临时目录 |
mkdirs() | 创建多级目录(如 a/b/c) | 创建嵌套文件夹结构 |
delete() | 删除文件/空目录(非空目录需先删内容) | 清理临时文件 |
exists() | 判断文件/目录是否存在 | 读取文件前检查存在性 |
isFile()/isDirectory() | 判断是否为文件/目录 | 遍历目录时区分文件和子目录 |
getName()/getPath() | 获取文件名/路径 | 显示文件列表 |
length() | 获取文件大小(字节数) | 检查文件是否为空 |
1.3 listFiles方法的注意事项
listFiles() 用于获取目录下的所有文件/子目录,返回 File[] 数组。注意事项:
- 必须是目录:若调用者是文件或不存在,返回
null,需先判断isDirectory()。 - 权限问题:若目录无访问权限,返回
null。 - 性能考虑:目录下文件过多时,数组占用内存较大,建议分批处理。
代码案例:
public class ListFilesDemo {
public static void main(String[] args) {
File dir = new File("C:/Users/Desktop");
if (dir.isDirectory()) { // 先判断是否为目录
File[] files = dir.listFiles(); // 获取目录下所有文件/子目录
if (files != null) { // 避免null指针异常
for (File file : files) {
// 区分文件和目录,显示名称和类型
String type = file.isFile() ? "文件" : "目录";
System.out.println(type + ":" + file.getName());
}
}
}
}
}
2. 递归:自身调用解决层级问题
2.1 什么是递归?
递归是方法自身调用自身的编程技巧,用于解决具有重复子问题和层级结构的问题(如目录遍历、树形结构处理)。
形式:方法内部包含对自身的调用;
注意事项:必须有终结条件,否则会导致栈溢出(StackOverflowError)。
2.2 递归三要素
- 公式:递归问题的分解(
f(n) = n + f(n-1)); - 终结点:停止递归的条件(如
n == 0时返回 0); - 走向终结点:每次递归需让参数接近终结点(如
n逐渐减小)。
2.3 案例:根据文件名称查找文件
需求:递归遍历指定目录下所有文件,找到名称包含“笔记”的文件。
代码案例:
public class FileSearchByRecursion {
public static void main(String[] args) {
File dir = new File("C:/Users/Desktop"); // 起始目录
searchFile(dir, "笔记"); // 调用递归方法查找包含"笔记"的文件
}
/**
* 递归查找文件
* @param dir 要遍历的目录
* @param keyword 文件名关键字
*/
private static void searchFile(File dir, String keyword) {
// 终结条件1:目录不存在,直接返回
if (dir == null || !dir.exists()) return;
// 终结条件2:若当前是文件,判断是否包含关键字
if (dir.isFile()) {
if (dir.getName().contains(keyword)) {
System.out.println("找到文件:" + dir.getAbsolutePath());
}
return; // 文件无需继续递归,返回
}
// 若当前是目录,获取所有子文件/子目录
File[] files = dir.listFiles();
if (files == null) return; // 权限不足或为空目录,返回
// 递归处理每个子文件/子目录(走向终结点:层级逐渐深入)
for (File file : files) {
searchFile(file, keyword); // 自身调用,处理子元素
}
}
}
执行流程:从起始目录开始,若为目录则遍历所有子元素,若为文件则检查名称,直到遍历完所有层级。
3. 字符集:字符与字节的映射规则
3.1 什么是字符集?
字符集(Charset)是字符与二进制字节的对应规则表,用于解决“如何用计算机存储文字”的问题。不同字符集支持的字符范围和编码方式不同,错误使用会导致乱码。
3.2 常见字符集介绍
| 字符集 | 起源与特点 | 支持语言 | 存储一个汉字所需字节 |
|---|---|---|---|
| ASCII | 美国标准,仅包含英文字母、数字和符号 | 英语 | 不支持汉字 |
| GBK | 中国国家标准,兼容ASCII,扩展支持中文 | 中文、英文 | 2字节 |
| Unicode | 国际标准,包含全球所有语言字符 | 所有语言 | 2字节(固定长度) |
| UTF-8 | Unicode的可变长度实现,节省空间 | 所有语言 | 1-4字节(中文通常3字节) |
3.3 UTF-8编码方案
UTF-8是目前最广泛使用的字符集,特点:
- 可变长度:根据字符范围使用1-4字节(英文字母1字节,中文3字节,生僻字4字节);
- 兼容ASCII:ASCII字符(0-127)用1字节表示,与ASCII完全兼容;
- 无字节序问题:无需BOM(字节顺序标记),适合网络传输和文件存储。
3.4 编码与解码
- 编码:
String → byte[],将字符转换为字节(需指定字符集); - 解码:
byte[] → String,将字节转换为字符(需与编码字符集一致,否则乱码)。
代码案例:
public class CharsetDemo {
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "你好,Java!";
// 1. 编码:字符串 → 字节数组(使用UTF-8)
byte[] utf8Bytes = str.getBytes("UTF-8");
// 2. 解码:字节数组 → 字符串(使用UTF-8,与编码一致)
String utf8Str = new String(utf8Bytes, "UTF-8");
System.out.println("UTF-8解码结果:" + utf8Str); // 结果:你好,Java!
// 3. 错误示例:用GBK解码UTF-8字节(乱码)
String gbkStr = new String(utf8Bytes, "GBK");
System.out.println("GBK错误解码结果:" + gbkStr); // 结果:浣犲ソ锛屽璞★紒(乱码)
}
}
4. IO流:数据传输的管道
4.1 认识IO流
IO流(Input/Output Stream)是Java用于读写数据的管道,数据从数据源(如文件、网络)通过“流”传输到程序,或从程序传输到目标(如文件、控制台)。
4.2 IO流的分类
按数据流向分:
- 输入流:数据从外部进入程序(读操作,如
InputStream、Reader); - 输出流:数据从程序到外部(写操作,如
OutputStream、Writer)。
按数据单位分:
- 字节流:以字节(1byte)为单位传输,可处理所有类型数据(文本、图片、视频等);
- 字符流:以字符为单位传输,仅处理文本数据(需指定字符集,避免乱码)。
按流的角色分:
- 节点流:直接连接数据源/目标的流(如
FileInputStream直接读文件); - 处理流:包装节点流,增强功能(如缓冲流提升性能,转换流处理字符集)。
4.3 IO流体系与实现类
核心父类:
- 字节流:
InputStream(输入)、OutputStream(输出); - 字符流:
Reader(输入)、Writer(输出)。
常用实现类:
| 流类型 | 节点流 | 处理流 |
|---|---|---|
| 字节输入流 | FileInputStream | BufferedInputStream(缓冲) |
| 字节输出流 | FileOutputStream | BufferedOutputStream(缓冲) |
| 字符输入流 | FileReader | BufferedReader(缓冲)、InputStreamReader(转换) |
| 字符输出流 | FileWriter | BufferedWriter(缓冲)、OutputStreamWriter(转换) |
5. 字节流:处理所有类型数据
5.1 字节输入流(InputStream)
作用:从数据源读取字节数据(如文件、网络)。
核心方法:
read():读取单个字节(返回字节值,-1表示结束);read(byte[] b):读取多个字节到数组(返回实际读取字节数,-1表示结束);close():关闭流,释放资源。
使用步骤:
- 创建流对象(关联数据源);
- 调用
read()读取数据; - 关闭流(必须释放资源)。
代码案例:读取图片文件(字节流处理非文本数据)
public class FileInputStreamDemo {
public static void main(String[] args) {
InputStream is = null;
try {
// 1. 创建字节输入流对象,关联图片文件
is = new FileInputStream("C:/Users/Desktop/pic.jpg");
// 2. 读取数据(使用byte数组缓冲,减少IO次数)
byte[] buffer = new byte[1024]; // 每次读取1024字节(1KB)
int len; // 记录实际读取的字节数
while ((len = is.read(buffer)) != -1) { // 循环读取,直到返回-1(文件结束)
System.out.println("读取到" + len + "字节数据");
// 此处可处理数据(如写入输出流)
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭流(必须在finally中执行,确保异常时也能关闭)
if (is != null) { // 避免null指针异常
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
5.2 字节输出流(OutputStream)
作用:将字节数据写入目标(如文件、网络)。
核心方法:
write(int b):写入单个字节;write(byte[] b):写入字节数组;write(byte[] b, int off, int len):写入数组的部分字节(从off开始,共len个);close():关闭流。
代码案例:写入文本到文件(字节流处理文本需注意字符集)
public class FileOutputStreamDemo {
public static void main(String[] args) {
OutputStream os = null;
try {
// 1. 创建字节输出流对象,关联目标文件(append: true表示追加内容,false表示覆盖)
os = new FileOutputStream("C:/Users/Desktop/log.txt", true);
// 2. 写入数据(字符串需先编码为字节数组,指定UTF-8字符集)
String content = "程序启动成功!\n";
byte[] data = content.getBytes("UTF-8"); // 编码为字节数组
os.write(data); // 写入字节数组
System.out.println("写入完成");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭流
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
5.3 案例:文件复制(字节流经典应用)
需求:将一张图片从C:/source.jpg复制到D:/target.jpg。
代码案例:
public class FileCopyByByteStream {
public static void main(String[] args) {
// 1. 声明输入流和输出流(作用域需覆盖try-catch-finally)
InputStream is = null;
OutputStream os = null;
try {
// 2. 创建流对象,关联源文件和目标文件
is = new FileInputStream("C:/source.jpg");
os = new FileOutputStream("D:/target.jpg");
// 3. 缓冲数组(一次读取1024KB,提升效率)
byte[] buffer = new byte[1024 * 1024]; // 1MB缓冲
int len; // 实际读取字节数
// 4. 循环读取并写入
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len); // 写入实际读取的字节(避免写入缓冲数组中的无效数据)
}
System.out.println("文件复制完成!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 关闭流(先关输出流,再关输入流,避免资源泄漏)
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
5.4 资源释放方案
资源:实现了Closeable或AutoCloseable接口的对象(如流、数据库连接),需手动关闭,否则会导致资源泄漏。
方案1:try-catch-finally(传统方式)
如上述案例,在finally中关闭流,确保异常时也能释放资源。
方案2:try-with-resource(JDK 7+,推荐)
语法:将资源声明在try()中,程序结束后自动关闭资源,无需手动close()。
代码案例:
public class TryWithResourceDemo {
public static void main(String[] args) {
// 资源声明在try()中,自动关闭(多个资源用;分隔)
try (InputStream is = new FileInputStream("C:/source.jpg");
OutputStream os = new FileOutputStream("D:/target.jpg")) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
System.out.println("复制完成(try-with-resource方式)");
} catch (IOException e) { // 无需finally,资源自动关闭
e.printStackTrace();
}
}
}
6. 字符流:专门处理文本数据
6.1 字符输入流(Reader)
作用:按字符读取文本数据(自动处理字节→字符的转换,需指定字符集)。
常用实现类:FileReader(简单文本读取)、BufferedReader(缓冲字符流,提升性能)。
核心方法:
read():读取单个字符(返回字符的Unicode值,-1表示结束);read(char[] cbuf):读取字符到数组;close():关闭流。
代码案例:用FileReader读取文本文件
public class FileReaderDemo {
public static void main(String[] args) {
// try-with-resource方式声明流,自动关闭
try (Reader reader = new FileReader("C:/Users/Desktop/note.txt", StandardCharsets.UTF_8)) {
char[] buffer = new char[1024]; // 字符数组缓冲
int len;
while ((len = reader.read(buffer)) != -1) {
// 将字符数组转换为字符串(取前len个有效字符)
String content = new String(buffer, 0, len);
System.out.print(content); // 打印读取内容
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
6.2 字符输出流(Writer)
作用:按字符写入文本数据(自动处理字符→字节的转换,需指定字符集)。
常用实现类:FileWriter(简单文本写入)、BufferedWriter(缓冲字符流)。
核心方法:
write(int c):写入单个字符;write(char[] cbuf):写入字符数组;write(String str):写入字符串(最常用);flush():刷新缓冲区(将内存数据强制写入目标);close():关闭流(会自动调用flush())。
注意事项:flush()与close()的区别
flush():仅刷新缓冲区,流可继续使用(如多次写入后需立即保存);close():刷新缓冲区+关闭流,流不可再使用。
代码案例:用FileWriter写入文本
public class FileWriterDemo {
public static void main(String[] args) {
try (Writer writer = new FileWriter("C:/Users/Desktop/log.txt", StandardCharsets.UTF_8, true)) {
writer.write("用户登录成功\n"); // 写入字符串
writer.write("操作时间:" + LocalDateTime.now() + "\n");
// 若需立即保存(如日志实时写入),调用flush()
writer.flush(); // 强制刷新缓冲区,确保数据写入文件
System.out.println("写入完成");
} catch (IOException e) {
e.printStackTrace();
}
// 无需close(),try-with-resource自动关闭(会先flush())
}
}
7. 缓冲流:提升IO性能的利器
7.1 缓冲流的作用与原理
作用:通过内存缓冲区减少物理IO次数,提升读写性能(尤其是大文件操作)。
原理:普通流每次读写1个字节/字符(频繁访问磁盘),缓冲流先将数据读入内存缓冲区(如8KB),缓冲区满后再一次性读写,减少磁盘访问次数。
7.2 缓冲字节流(BufferedInputStream/BufferedOutputStream)
使用方式:包装普通字节流,无额外API,用法与普通字节流一致。
代码案例:缓冲流复制大文件(性能对比普通流提升显著)
public class BufferedByteStreamDemo {
public static void main(String[] args) {
try (// 缓冲流包装节点流
InputStream is = new BufferedInputStream(new FileInputStream("C:/largefile.zip"));
OutputStream os = new BufferedOutputStream(new FileOutputStream("D:/copy.zip"))) {
byte[] buffer = new byte[1024 * 1024]; // 1MB缓冲(缓冲流内部已有缓冲区,此处可进一步优化)
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
System.out.println("大文件复制完成(缓冲字节流)");
} catch (IOException e) {
e.printStackTrace();
}
}
}
7.3 缓冲字符流(BufferedReader/BufferedWriter)
特有方法:
BufferedReader.readLine():读取一行文本(不含换行符,返回null表示结束);BufferedWriter.newLine():写入系统兼容的换行符(Windows:\r\n,Linux:\n)。
代码案例:读取文本文件并按行处理
public class BufferedCharStreamDemo {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("C:/poem.txt", StandardCharsets.UTF_8));
BufferedWriter bw = new BufferedWriter(new FileWriter("C:/poem_copy.txt", StandardCharsets.UTF_8))) {
String line; // 存储每行内容
// 按行读取(readLine()返回null时结束)
while ((line = br.readLine()) != null) {
System.out.println("读取行:" + line);
bw.write(line); // 写入行内容
bw.newLine(); // 写入换行符(跨平台兼容)
}
System.out.println("文本复制完成(缓冲字符流)");
} catch (IOException e) {
e.printStackTrace();
}
}
}
7.4 提升IO性能的总结
- 使用缓冲流:优先选择
BufferedXXX系列,减少物理IO次数; - 合理设置缓冲区大小:默认8KB,大文件可适当增大(如1MB);
- 使用数组缓冲:读取时用
byte[]/char[],减少循环次数; - 减少流的创建次数:避免在循环中创建流对象;
- 及时关闭流:释放资源,避免占用系统句柄。
8. 其他流:特殊场景的解决方案
8.1 字符输入转换流(InputStreamReader)
作用:将字节输入流转换为字符输入流,并指定字符集(解决文本文件乱码问题)。
场景:读取GBK编码的文本文件(FileReader默认使用系统字符集,可能乱码)。
代码案例:读取GBK编码文件
public class InputStreamReaderDemo {
public static void main(String[] args) {
try (// 字节流→字符流转换,指定GBK字符集
InputStreamReader isr = new InputStreamReader(new FileInputStream("C:/gbk_file.txt"), "GBK");
BufferedReader br = new BufferedReader(isr)) { // 包装为缓冲流提升性能
String line;
while ((line = br.readLine()) != null) {
System.out.println("GBK文件内容:" + line); // 正确解码,无乱码
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
8.2 打印流(PrintStream/PrintWriter)
作用:方便输出各种数据类型(如int、String、对象),自动转换为字符串,无需手动编码。
特点:
PrintStream:字节流,默认输出到控制台(System.out就是PrintStream);PrintWriter:字符流,可指定字符集,支持写入文件。
代码案例:用PrintWriter写入日志
public class PrintWriterDemo {
public static void main(String[] args) {
try (PrintWriter pw = new PrintWriter("C:/app.log", StandardCharsets.UTF_8)) {
pw.println("===== 系统日志 ====="); // 写入字符串(自动换行)
pw.printf("启动时间:%s%n", LocalDateTime.now()); // 格式化输出(%n是跨平台换行符)
pw.println("状态:正常运行");
pw.flush(); // 立即写入(或使用autoFlush参数)
} catch (IOException e) {
e.printStackTrace();
}
}
}
8.3 数据输入输出流(DataInputStream/DataOutputStream)
作用:读写基本数据类型(如int、double、boolean),保持数据类型信息(普通字节流仅读写字节,丢失类型)。
特点:收/发必须对应(写入顺序与读取顺序一致)。
代码案例:写入并读取基本数据类型
public class DataStreamDemo {
public static void main(String[] args) throws IOException {
// 写入基本数据类型
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"))) {
dos.writeInt(2024); // 写入int(4字节)
dos.writeDouble(3.14); // 写入double(8字节)
dos.writeBoolean(true); // 写入boolean(1字节)
}
// 读取基本数据类型(顺序必须与写入一致)
try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {
int year = dis.readInt();
double pi = dis.readDouble();
boolean flag = dis.readBoolean();
System.out.println("读取结果:" + year + ", " + pi + ", " + flag); // 结果:2024, 3.14, true
}
}
}
9. Commons-io框架:简化IO操作
9.1 框架介绍
Commons-io是Apache提供的IO工具类库,封装了Java原生IO的复杂操作,提供简洁API,减少重复代码。
依赖方式:Maven项目添加依赖:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version> <!-- 最新版本 -->
</dependency>
手动添加:https://commons.apache.org/proper/commons-io/download_io.cgi下载解压后在项目创建lib目录,导入jar包即可
9.2 常用方法(FileUtils类)
| 方法名 | 作用 | 原生IO实现复杂度 |
|---|---|---|
copyFile(File src, File dest) | 复制文件 | 需手动处理流、缓冲 |
deleteDirectory(File dir) | 删除目录(含所有子文件/子目录) | 需递归删除 |
readFileToString(File file, Charset charset) | 读取文件内容为字符串 | 需字符流+缓冲+编码 |
writeStringToFile(File file, String data, Charset charset) | 写入字符串到文件 | 需字符流+编码 |
listFiles(File dir, String[] extensions) | 按扩展名筛选文件 | 需遍历+字符串判断 |
代码案例:用Commons-io复制文件
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.nio.charset.StandardCharsets;
public class CommonsIODemo {
public static void main(String[] args) {
try {
File src = new File("C:/source.txt");
File dest = new File("D:/dest.txt");
// 1. 复制文件(一行代码,无需处理流)
FileUtils.copyFile(src, dest);
System.out.println("文件复制完成(Commons-io)");
// 2. 读取文件内容为字符串(指定UTF-8)
String content = FileUtils.readFileToString(src, StandardCharsets.UTF_8);
System.out.println("文件内容:" + content);
// 3. 写入字符串到文件
FileUtils.writeStringToFile(dest, "追加内容", StandardCharsets.UTF_8, true); // true表示追加
} catch (IOException e) {
e.printStackTrace();
}
}
}
优势:Commons-io将原生IO的“创建流→缓冲→读写→关闭流”等步骤封装为单方法调用,极大简化代码,减少出错概率。

991

被折叠的 条评论
为什么被折叠?



