十四、Java入门进阶:File、递归、字符集与各类 IO 流
对于 Java 入门学习者来说,IO 操作是日常开发的必备技能,从文件的创建删除、到数据的读写、再到高效的流处理,都是项目中高频用到的功能。本文会从基础的 File 类讲起,逐步讲解递归、字符集,再深入各类 IO 流的使用。
目录
4.2.2 文件字节输出流:FileOutputStream
方式 2:try-with-resources(自动关闭流,推荐)
一、File 类:
在 Java 中,File 类是
java.io包下的核心类,专门用来表示文件或文件夹的路径,并提供了创建、删除、查询文件 / 文件夹信息的方法。注意:File 类只能操作文件的属性,不能读写文件里的内容,读写内容需要靠后续的 IO 流。
1.1 核心作用
- 表示文件(.txt/.java/.jpg)或文件夹的路径(绝对路径 / 相对路径);
- 实现文件 / 文件夹的创建、删除、重命名;
- 查询文件 / 文件夹的是否存在、大小、最后修改时间等信息;
- 遍历文件夹下的所有文件 / 子文件夹。
1.2 创建 / 删除 文件 / 文件夹
先记住两个关键路径概念:
- 绝对路径:从磁盘根目录开始的完整路径,如
D:\java\test.txt。 - 相对路径:相对于当前项目的路径,如
test.txt、src/file/test.txt。
核心方法表
| 方法名 | 作用 | 注意事项 |
|---|---|---|
createNewFile() |
创建文件,成功返回 true,文件已存在返回 false | 会抛出编译时异常IOException,需处理 |
mkdir() |
创建单级文件夹 | 父文件夹不存在则创建失败 |
mkdirs() |
创建多级文件夹 | 父文件夹不存在会自动创建,推荐使用 |
delete() |
删除文件 / 空文件夹 | 无法删除非空文件夹,删除后不进回收站 |
exists() |
判断文件 / 文件夹是否存在 | 常用作创建 / 删除前的判断 |
代码示例
import java.io.File;
import java.io.IOException;
public class FileDemo {
public static void main(String[] args) throws IOException {
// 1. 构建File对象,指向一个文件(相对路径,当前项目下的test.txt)
File file = new File("test.txt");
// 构建File对象指向文件夹
File dir = new File("src/file/testDir");
File dirs = new File("src/file/a/b/c");
// 2. 创建文件:先判断是否存在,不存在再创建
if (!file.exists()) {
boolean isCreate = file.createNewFile();
System.out.println("文件创建是否成功:" + isCreate);
}
// 3. 创建单级文件夹:父文件夹src/file需存在,否则失败
if (!dir.exists()) {
boolean isMkdir = dir.mkdir();
System.out.println("单级文件夹创建是否成功:" + isMkdir);
}
// 4. 创建多级文件夹:自动创建父文件夹,推荐
if (!dirs.exists()) {
boolean isMkdirs = dirs.mkdirs();
System.out.println("多级文件夹创建是否成功:" + isMkdirs);
}
// 5. 删除文件/文件夹:删除空文件夹
boolean isDelFile = file.delete();
System.out.println("文件删除是否成功:" + isDelFile);
boolean isDelDir = dir.delete();
System.out.println("空文件夹删除是否成功:" + isDelDir);
}
}
1.3 遍历文件夹下的文件
通过listFiles()方法获取文件夹下的所有文件 / 子文件夹对象,实现遍历:
// 遍历src/file文件夹下的所有内容
File parentDir = new File("src/file");
if (parentDir.exists() && parentDir.isDirectory()) { // 判断是文件夹
File[] files = parentDir.listFiles(); // 获取所有子文件/文件夹
for (File f : files) {
if (f.isFile()) {
System.out.println("文件:" + f.getName() + ",大小:" + f.length() + "字节");
} else if (f.isDirectory()) {
System.out.println("文件夹:" + f.getName());
}
}
}
二、方法递归:
递归是 Java 中的一种算法思想,核心是方法自己调用自己,常用于解决层级化的问题(如文件搜索、树形结构遍历),也是 IO 操作中遍历文件夹的常用手段。
2.1 核心形式 & 必备条件
基本形式
public static void 递归方法(参数) {
// 1. 终止条件:必须有,否则会出现栈溢出错误(StackOverflowError)
if (条件) {
return;
}
// 2. 递归体:方法自己调用自己,且参数要逐步靠近终止条件
递归方法(变化后的参数);
}
两个必备条件
- 有终止条件:防止方法无限递归,导致 JVM 栈内存溢出;
- 参数递推:每次递归的参数要逐步靠近终止条件。
2.2 递归的执行流程:
-
层层调用,层层返回
用一个简单的求 1-n 的和案例理解执行流程:
public class RecursionDemo {
// 求1-n的和
public static int sum(int n) {
// 终止条件:n=1时,和为1
if (n == 1) {
return 1;
}
// 递归体:sum(n) = n + sum(n-1)
return n + sum(n - 1);
}
public static void main(String[] args) {
System.out.println(sum(3)); // 结果:6
}
}
执行流程:
sum(3)→ 3 +sum(2)sum(2)→ 2 +sum(1)sum(1)→ 触发终止条件,返回 1- 层层返回:
sum(2)=2+1=3→sum(3)=3+3=6
2.3 猴子吃桃问题
问题描述
猴子第一天摘下若干桃子,当即吃了一半,还多吃了一个;第二天又将剩下的桃子吃了一半,又多吃一个;以后每天都吃前一天剩下的一半加一个,到第 10 天早上发现只剩 1 个桃子。求第一天摘了多少个桃子?
解题思路
- 设第 n 天的桃子数为
f(n),第 n+1 天为f(n+1); - 递推公式:
f(n) = (f(n+1) + 1) * 2(每天吃一半加 1,反推前一天); - 终止条件:
f(10) = 1(第 10 天只剩 1 个)。
代码实现
public class MonkeyEatPeach {
// 求第n天的桃子数
public static int getPeach(int n) {
// 终止条件:第10天只剩1个
if (n == 10) {
return 1;
}
// 递推公式:前一天 = (后一天 +1) *2
return (getPeach(n + 1) + 1) * 2;
}
public static void main(String[] args) {
System.out.println("第一天摘的桃子数:" + getPeach(1)); // 结果:1534
}
}
2.4 文件搜索
问题描述
递归搜索指定文件夹下的所有指定后缀名的文件(如搜索
D:\java下所有.java文件)。
解题思路
- 传入目标文件夹,遍历其下的所有文件 / 子文件夹;
- 如果是文件,判断后缀名是否符合,符合则打印路径;
- 如果是文件夹,递归调用自身,继续遍历;
- 终止条件:遍历到的是文件,无下级内容。
代码实现
import java.io.File;
public class FileSearch {
/**
* 递归搜索文件
* @param dir 目标文件夹
* @param suffix 要搜索的文件后缀(如.java)
*/
public static void searchFile(File dir, String suffix) {
// 校验:文件夹是否存在
if (!dir.exists() || !dir.isDirectory()) {
System.out.println("文件夹不存在或不是文件夹!");
return;
}
// 遍历文件夹下的所有内容
File[] files = dir.listFiles();
if (files == null) return; // 防止空指针
for (File f : files) {
if (f.isFile()) {
// 是文件:判断后缀名
if (f.getName().endsWith(suffix)) {
System.out.println("找到目标文件:" + f.getAbsolutePath());
}
} else {
// 是文件夹:递归搜索
searchFile(f, suffix);
}
}
}
public static void main(String[] args) {
// 搜索D:\java下的所有.java文件
File targetDir = new File("D:\\java");
searchFile(targetDir, ".java");
}
}
三、字符集:
在 IO 流读写文本文件时,乱码是最常见的问题,而乱码的根源就是编码和解码使用的字符集不一致。字符集是一套字符与字节的映射规则,简单说:把文字转成字节数组是编码,把字节数组转回文字是解码,只有规则一致,才能正确转换。
3.1 常见字符集
| 字符集 | 特点 | 适用场景 |
|---|---|---|
| ASCII | 美国标准码,仅包含英文字母、数字、符号,占1 个字节 | 纯英文场景 |
| GBK | 国标码,兼容 ASCII,中文占2 个字节,英文占 1 个字节 | 中文 Windows 系统默认 |
| UTF-8 | 万国码,兼容 ASCII,中文占3 个字节,英文占 1 个字节 | 项目开发、互联网、Linux 系统默认,推荐使用 |
3.2 乱码的根本原因
编码字符集 ≠ 解码字符集,比如:
- 用 GBK 把 “中国” 编码成字节数组,再用 UTF-8 解码,就会出现乱码;
- 用 UTF-8 编码,再用 UTF-8 解码,才能正确显示。
3.3 字符集编码 & 解码操作
Java 中String类提供了直接的编码和解码方法,核心方法:
- 编码:
String → byte[]:getBytes(字符集) - 解码:
byte[] → String:new String(字节数组, 字符集)
注意:字符集参数要写规范名称(如"UTF-8"、"GBK"),不能写错。
代码示例:
public class CharsetDemo {
public static void main(String[] args) throws Exception {
String str = "中国";
// 1. 编码:UTF-8编码成字节数组
byte[] utf8Bytes = str.getBytes("UTF-8");
// 编码:GBK编码成字节数组
byte[] gbkBytes = str.getBytes("GBK");
// 2. 解码:用对应字符集解码 → 正确
String utf8Str = new String(utf8Bytes, "UTF-8");
String gbkStr = new String(gbkBytes, "GBK");
System.out.println("UTF-8解码:" + utf8Str); // 中国
System.out.println("GBK解码:" + gbkStr); // 中国
// 3. 解码:字符集不一致 → 乱码
String errorStr = new String(utf8Bytes, "GBK");
System.out.println("UTF-8编码+GBK解码:" + errorStr); // 乱码:涓浗
}
}
结论:IO 读写文本文件时,必须保证编码和解码的字符集一致,推荐全程使用 UTF-8。
四、IO 流:
File 类只能操作文件属性,而IO 流(Input/Output)是专门用来读写文件内容的工具,是
java.io包的核心。IO 流的本质是数据的传输,把数据从文件读到程序是输入流,把程序的数据写到文件是输出流。
4.1 分类
IO 流有两大核心分类维度:
- 按数据类型分:字节流(操作所有文件)、字符流(仅操作文本文件);
- 按流向分:输入流(读数据)、输出流(写数据)。
核心基类(所有流都继承这些基类,抽象类,不能直接使用):
- 字节输入流:
InputStream - 字节输出流:
OutputStream - 字符输入流:
Reader - 字符输出流:
Writer
4.2 字节流:
字节流以字节为单位读写数据,能操作所有类型的文件(文本、图片、视频、音频等),是最基础的 IO 流。
核心实现类:FileInputStream(文件字节输入流)、FileOutputStream(文件字节输出流)。
4.2.1 文件字节输入流:FileInputStream
核心作用:把本地文件的字节数据读到 Java 程序中。
核心方法:
read():一次读 1 个字节,返回读到的字节值,读到末尾返回-1;read(byte[] b):一次读一个字节数组,返回读到的有效字节数,读到末尾返回-1(推荐,效率更高);close():关闭流,释放资源(必须做,否则会占用系统资源)。
4.2.2 文件字节输出流:FileOutputStream
核心作用:把 Java 程序中的字节数据写到本地文件中。
核心方法:
write(int b):一次写 1 个字节;write(byte[] b):一次写一个字节数组(推荐);write(byte[] b, int off, int len):一次写字节数组的指定部分;close():关闭流,释放资源;
注意:创建 FileOutputStream 时,new FileOutputStream("test.txt")会覆盖原有文件,如果要追加写入,用new FileOutputStream("test.txt", true)。
4.2.3 字节流实现文件复制
文件复制的本质是先读再写:用 FileInputStream 读源文件的字节,用 FileOutputStream 把字节写到目标文件,支持复制任意类型文件(如图片、视频、文本)。
方式 1:try-catch-finally(手动关闭流)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFileByByte {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 1. 创建流对象:源文件(要复制的文件)、目标文件(复制后的文件)
fis = new FileInputStream("src/file/1.jpg"); // 源文件
fos = new FileOutputStream("src/file/1_copy.jpg"); // 目标文件
// 2. 读写数据:一次读1024字节(1KB),效率更高
byte[] buffer = new byte[1024];
int len; // 记录每次读到的有效字节数
while ((len = fis.read(buffer)) != -1) {
// 写数据:只写读到的有效字节,避免写空字节
fos.write(buffer, 0, len);
}
System.out.println("文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭流:先关输出流,再关输入流,防止空指针
try {
if (fos != null) fos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
方式 2:try-with-resources(自动关闭流,推荐)
Java7 提供了自动关闭资源的语法,流对象写在try()中,程序执行完会自动调用close(),无需手动关闭,代码更简洁:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFileByByte2 {
public static void main(String[] args) {
// 流对象写在try()中,自动关闭
try (FileInputStream fis = new FileInputStream("src/file/1.jpg");
FileOutputStream fos = new FileOutputStream("src/file/1_copy2.jpg")) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意:只有实现了AutoCloseable接口的类才能用这个语法,IO 流都实现了该接口。
4.3 字符流:
字符流以字符为单位读写数据,底层还是字节流,只是帮我们做了字符集的转换,仅能操作文本文件(.txt/.java/.xml 等),操作非文本文件会损坏文件。
核心实现类:FileReader(字符输入流)、FileWriter(字符输出流)。
核心特点
- 自动适配系统默认字符集,也可以手动指定(JDK8+);
- 读写文本文件更简洁,无需手动处理字节数组;
- 追加写入:
new FileWriter("test.txt", true)。
案例:字符流读写文本文件
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CharStreamDemo {
public static void main(String[] args) {
// 自动关闭流
try (FileReader fr = new FileReader("src/file/test.txt"); // 读文本
FileWriter fw = new FileWriter("src/file/test_copy.txt", true)) { // 写文本,追加
// 1. 读数据:一次读1个字符数组
char[] buffer = new char[1024];
int len;
while ((len = fr.read(buffer)) != -1) {
// 2. 写数据
fw.write(buffer, 0, len);
}
// 刷新流:强制把缓冲区的数据写到文件(字符流有缓冲区)
fw.flush();
System.out.println("文本文件读写成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意:字符流有缓冲区,写数据时建议调用flush(),确保数据全部写入文件。
4.4 缓冲流:
无论是字节流还是字符流,直接读写的效率都不高,因为每次都会和磁盘交互。缓冲流就是为了解决这个问题,它在底层流的基础上增加了缓冲区(内存中的一块区域),先把数据读到缓冲区,再一次性读写,减少磁盘交互次数,提升效率 5-10 倍。
缓冲流是包装流,需要包装底层的字节流 / 字符流才能使用,核心实现类:
字节缓冲流
BufferedInputStream:包装字节输入流BufferedOutputStream:包装字节输出流
字符缓冲流
BufferedReader:包装字符输入流,特有readLine()方法(一次读一行)BufferedWriter:包装字符输出流,特有newLine()方法(换行)
4.4.1 字节缓冲流复制文件
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class BufferedByteDemo {
public static void main(String[] args) {
// 缓冲流包装底层字节流
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/file/2.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/file/2_copy.mp4"))) {
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
System.out.println("视频文件复制成功(缓冲字节流)!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.4.2 字符缓冲流读写文本
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
public class BufferedCharDemo {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("src/file/test.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("src/file/test_buff.txt"))) {
String line;
// 特有方法:readLine() 一次读一行,读到末尾返回null
while ((line = br.readLine()) != null) {
bw.write(line); // 写一行
bw.newLine(); // 特有方法:换行(跨平台,Windows/Linux都适用)
}
System.out.println("文本读写成功(缓冲字符流)!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.4.3 字节流 & 缓冲流的性能对比
| 流类型 | 读写方式 | 效率 | 适用场景 |
|---|---|---|---|
| 普通字节流 | 直接和磁盘交互 | 低 | 小文件读写 |
| 缓冲字节流 | 缓冲区读写,减少磁盘交互 | 高 | 大文件读写(图片、视频、大文本),推荐 |
结论:实际开发中,无论操作什么文件,优先使用缓冲流,效率远高于普通流。
4.5 其他常用流:
除了基础流和缓冲流,Java 还提供了多种特殊流,解决不同的业务场景,掌握以下几种即可满足 90% 的开发需求。
4.5.1 转换流:解决字符流的编码问题
FileReader/FileWriter默认使用系统字符集,无法手动指定,当系统字符集和文件字符集不一致时,会出现乱码。转换流(
InputStreamReader/OutputStreamWriter)是字节流和字符流的桥梁,可以手动指定字符集,彻底解决字符流的乱码问题。
核心作用:手动指定字符集,实现字节流到字符流的转换。
案例:转换流读写 UTF-8 编码的文本文件
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class ConvertStreamDemo {
public static void main(String[] args) {
// 转换流:指定UTF-8字符集
try (InputStreamReader isr = new InputStreamReader(new FileInputStream("src/file/utf8.txt"), "UTF-8");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("src/file/utf8_copy.txt"), "UTF-8")) {
char[] buffer = new char[1024];
int len;
while ((len = isr.read(buffer)) != -1) {
osw.write(buffer, 0, len);
}
System.out.println("UTF-8文本读写成功,无乱码!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.5.2 打印流:
打印流(
PrintStream/PrintWriter)是开发中最常用的输出流,System.out.println () 的底层就是PrintStream。
核心特点:
- 支持打印任意数据类型(int/string/object 等);
- 自动刷新缓冲区,无需手动调用
flush(); - 可以输出到控制台,也可以输出到文件。
案例:打印流输出到文件
import java.io.PrintStream;
public class PrintStreamDemo {
public static void main(String[] args) {
// 打印流输出到文件,覆盖写入
try (PrintStream ps = new PrintStream("src/file/print.txt")) {
ps.println(100); // 打印整数
ps.println("Java IO"); // 打印字符串
ps.println(true); // 打印布尔值
ps.printf("姓名:%s,年龄:%d", "张三", 18); // 格式化打印
System.out.println("打印流输出成功!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.5.3 数据流:
数据流(
DataInputStream/DataOutputStream)可以直接读写 Java 的基本数据类型(int/long/double 等),无需手动转换为字节,适合存储程序的配置数据、基本类型数据。
案例:数据流读写基本数据类型
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class DataStreamDemo {
public static void main(String[] args) throws Exception {
// 1. 写基本数据类型到文件
DataOutputStream dos = new DataOutputStream(new FileOutputStream("src/file/data.txt"));
dos.writeInt(18);
dos.writeDouble(95.5);
dos.writeBoolean(true);
dos.close();
// 2. 读基本数据类型从文件(读的顺序要和写的顺序一致)
DataInputStream dis = new DataInputStream(new FileInputStream("src/file/data.txt"));
int age = dis.readInt();
double score = dis.readDouble();
boolean isPass = dis.readBoolean();
dis.close();
System.out.println("年龄:" + age + ",分数:" + score + ",是否及格:" + isPass);
}
}
4.6 commons-io 框架:
Java 原生的 IO 流代码比较繁琐,Apache 提供了commons-io框架,封装了大量的 IO 工具方法,一行代码就能实现文件复制、文件夹遍历、文件大小判断等操作,极大提升开发效率,是企业开发的标配框架。
4.6.1 下载 JAR 包
如果你的项目没有使用构建工具,可以手动下载 JAR 包并引入:
- 打开 Apache Commons IO 的官方下载页面:https://commons.apache.org/proper/commons-io/download_io.cgi
- 找到「Binary Downloads」(二进制下载)区域,选择带
-bin.zip后缀的包(比如commons-io-2.15.1-bin.zip),不要选-src.zip(源码包); - 下载后解压压缩包,找到里面的
commons-io-2.15.1.jar文件; - 将这个 JAR 包复制到你的 Java 项目中(比如新建
lib目录存放),并在 IDE 中「添加为库」即可。




- 核心工具类:
FileUtils(文件 / 文件夹操作)、IOUtils(流操作)。
4.6.2 一行代码实现文件 / 文件夹操作
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class CommonsIODemo {
public static void main(String[] args) throws Exception {
File srcFile = new File("src/file/1.jpg");
File destFile = new File("src/file/1_commons.jpg");
File dir = new File("src/file/test");
File textFile = new File("src/file/test.txt");
// 1. 一行代码复制文件(支持任意类型)
FileUtils.copyFile(srcFile, destFile);
// 2. 一行代码复制文件夹(包括子文件/子文件夹)
FileUtils.copyDirectory(dir, new File("src/file/test_copy"));
// 3. 一行代码读取文本文件的所有行(UTF-8)
List<String> lines = FileUtils.readLines(textFile, StandardCharsets.UTF_8);
// 4. 一行代码写入字符串到文本文件(追加)
FileUtils.writeStringToFile(textFile, "commons-io真好用!", StandardCharsets.UTF_8, true);
// 5. 一行代码删除文件夹(包括非空文件夹)
FileUtils.deleteDirectory(new File("src/file/test_copy"));
System.out.println("commons-io操作成功!");
}
}
结论:企业开发中,优先使用 commons-io 框架,替代原生 IO 流,减少重复代码。
五、核心知识点总结
- File 类:操作文件 / 文件夹的属性,创建 / 删除 / 遍历,不能读写内容;
- 递归:方法自己调自己,必备终止条件,用于文件搜索、树形遍历;
- 字符集:编码解码一致避免乱码,推荐使用 UTF-8,String 类实现编解码;
- IO 流:
- 字节流操作所有文件,字符流仅操作文本文件;
- 缓冲流包装底层流,提升读写效率,开发优先使用;
- 转换流解决字符流编码问题,打印流方便输出,数据流操作基本类型;
- commons-io 框架简化 IO 操作,一行代码实现核心功能。
更多推荐



所有评论(0)