Java:File类、递归、字符集、IO流体系及Commons-io框架
Java:File类、递归、字符集、IO流体系及Commons-io框架
- 1. File类:文件与目录的操作入口
- 1.1 File类的作用与创建对象
- 创建File对象:绝对路径与相对路径
- 1.2 操作文件和目录的常用方法
- 1.3 listFiles方法的注意事项
- 2. 递归:自身调用解决层级问题
- 2.1 什么是递归?
- 2.2 递归三要素
- 2.3 案例:根据文件名称查找文件
- 3. 字符集:字符与字节的映射规则
- 3.1 什么是字符集?
- 3.2 常见字符集介绍
- 3.3 UTF-8编码方案
- 3.4 编码与解码
- 4. IO流:数据传输的管道
- 4.1 认识IO流
- 4.2 IO流的分类
- 按数据流向分:
- 按数据单位分:
- 按流的角色分:
- 4.3 IO流体系与实现类
- 5. 字节流:处理所有类型数据
- 5.1 字节输入流(InputStream)
- 5.2 字节输出流(OutputStream)
- 5.3 案例:文件复制(字节流经典应用)
- 5.4 资源释放方案
- 方案1:try-catch-finally(传统方式)
- 方案2:try-with-resource(JDK 7+,推荐)
- 6. 字符流:专门处理文本数据
- 6.1 字符输入流(Reader)
- 6.2 字符输出流(Writer)
- 7. 缓冲流:提升IO性能的利器
- 7.1 缓冲流的作用与原理
- 7.2 缓冲字节流(BufferedInputStream/BufferedOutputStream)
- 7.3 缓冲字符流(BufferedReader/BufferedWriter)
- 7.4 提升IO性能的总结
- 8. 其他流:特殊场景的解决方案
- 8.1 字符输入转换流(InputStreamReader)
- 8.2 打印流(PrintStream/PrintWriter)
- 8.3 数据输入输出流(DataInputStream/DataOutputStream)
- 9. Commons-io框架:简化IO操作
- 9.1 框架介绍
- 9.2 常用方法(FileUtils类)
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的“创建流→缓冲→读写→关闭流”等步骤封装为单方法调用,极大简化代码,减少出错概率。