JAVA IO流进阶:字符流与字节流的深度应用

1.1 本章学习目标与重点
💡 掌握字节流与字符流的核心区别,能够根据实际开发场景选择合适的IO流实现文件操作。
💡 熟练运用缓冲流提升IO操作效率,解决大文件读写的性能问题。
💡 理解转换流的作用,处理不同编码格式的文件读写,避免乱码问题。
⚠️ 本章重点是流的嵌套使用和资源释放的标准写法,这是实际开发中高频考点和易错点。
1.2 字节流与字符流的核心差异(七千字以上内容展开)
1.2.1 基本概念与设计初衷
💡 字节流以byte为基本单位进行数据传输,它可以处理所有类型的文件,比如图片、视频、音频、文本等。
字符流以char为基本单位进行数据传输,它专门用于处理文本文件,底层会涉及字符编码的转换。
字节流的核心类是InputStream和OutputStream,字符流的核心类是Reader和Writer。
两者都是抽象类,实际开发中我们使用的是它们的子类,比如FileInputStream、FileWriter等。
✅ 核心结论:处理非文本文件用字节流,处理文本文件优先用字符流。
1.2.2 代码实操:字节流读写文本文件
① 📝 创建FileInputStream对象,关联要读取的文本文件test.txt
② 📝 定义byte数组作为缓冲区,减少IO次数
③ 📝 读取数据并转换为字符串,输出到控制台
④ 📝 关闭流资源,释放文件句柄
import java.io.FileInputStream;
import java.io.IOException;
public class ByteStreamDemo {
public static void main(String[] args) {
FileInputStream fis = null;
try {
// 1. 关联文件路径
fis = new FileInputStream("test.txt");
// 2. 定义缓冲区,大小为1024字节(1KB)
byte[] buffer = new byte[1024];
int len; // 记录每次读取的有效字节数
// 3. 循环读取数据
while ((len = fis.read(buffer)) != -1) {
// 将字节数组转换为字符串
System.out.print(new String(buffer, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭流资源
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
⚠️ 注意事项:使用字节流读取文本文件时,如果文件编码是UTF-8,而系统默认编码是GBK,可能会出现乱码。这时候需要用字符流或者转换流来解决。
1.2.3 代码实操:字符流读写文本文件
字符流的优势在于自动处理字符编码,默认使用系统编码,也可以手动指定编码格式。
下面是用FileReader和FileWriter实现文本文件的复制操作:
① 📝 创建FileReader对象读取源文件,创建FileWriter对象写入目标文件
② 📝 定义char数组作为缓冲区,提升读取效率
③ 📝 循环读取源文件数据,并写入目标文件
④ 📝 关闭流资源,先关写入流,再关读取流
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CharStreamDemo {
public static void main(String[] args) {
FileReader fr = null;
FileWriter fw = null;
try {
// 1. 关联源文件和目标文件
fr = new FileReader("source.txt");
fw = new FileWriter("target.txt");
// 2. 定义字符缓冲区
char[] buffer = new char[1024];
int len;
// 3. 循环读写
while ((len = fr.read(buffer)) != -1) {
fw.write(buffer, 0, len);
// 刷新缓冲区,避免数据滞留
fw.flush();
}
System.out.println("✅ 文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭流资源,后开先关
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
✅ 核心结论:字符流读写文本文件时,无需手动处理编码转换,代码更简洁,且不易出现乱码。
1.2.4 字节流与字符流的性能对比
💡 没有缓冲的情况下,字节流和字符流的读写效率相近。
但在处理大文件时,两者都需要搭配缓冲流来提升性能。
缓冲流的原理是在内存中开辟一块缓冲区,一次性读取或写入大量数据,减少与磁盘的交互次数。
字节缓冲流的类是BufferedInputStream和BufferedOutputStream。
字符缓冲流的类是BufferedReader和BufferedWriter。
下面是缓冲流的性能测试案例:
分别用普通字节流和缓冲字节流读取一个100MB的视频文件,记录耗时。
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class BufferedStreamTest {
public static void main(String[] args) {
long start = System.currentTimeMillis();
readWithBuffer("large_video.mp4");
long end = System.currentTimeMillis();
System.out.println("缓冲流耗时:" + (end - start) + "ms");
start = System.currentTimeMillis();
readWithoutBuffer("large_video.mp4");
end = System.currentTimeMillis();
System.out.println("普通流耗时:" + (end - start) + "ms");
}
// 使用缓冲字节流读取文件
private static void readWithBuffer(String path) {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path))) {
byte[] buffer = new byte[1024];
while (bis.read(buffer) != -1) {
// 读取数据,不做输出
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 使用普通字节流读取文件
private static void readWithoutBuffer(String path) {
try (FileInputStream fis = new FileInputStream(path)) {
byte[] buffer = new byte[1024];
while (fis.read(buffer) != -1) {
// 读取数据,不做输出
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
测试结果(仅供参考):
缓冲流耗时:120ms
普通流耗时:850ms
✅ 核心结论:缓冲流能大幅提升IO操作效率,处理大文件时必须使用缓冲流。
1.3 转换流:解决文件编码乱码问题
1.3.1 转换流的作用
💡 转换流的作用是字节流和字符流之间的转换,它可以指定字符编码格式,解决文本文件读写的乱码问题。
转换流的核心类是InputStreamReader和OutputStreamWriter。
InputStreamReader:将字节输入流转换为字符输入流。
OutputStreamWriter:将字符输出流转换为字节输出流。
1.3.2 代码实操:指定编码读取文件
当我们读取一个UTF-8编码的文件,而系统默认编码是GBK时,直接用FileReader会出现乱码。
此时可以用InputStreamReader指定编码格式为UTF-8:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class ConvertStreamDemo {
public static void main(String[] args) {
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream("utf8_file.txt"), "UTF-8")) {
char[] buffer = new char[1024];
int len;
while ((len = isr.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
⚠️ 注意事项:指定的编码格式必须和文件的实际编码一致,否则仍然会出现乱码。
常见的编码格式有UTF-8、GBK、GB2312、ISO-8859-1等。
1.4 IO流资源释放的标准写法
1.4.1 JDK7之前的写法:try-catch-finally
在JDK7之前,我们需要在finally块中手动关闭流资源,并且要判断流对象是否为null,避免空指针异常。
这种写法比较繁琐,但兼容性最好。
1.4.2 JDK7及之后的写法:try-with-resources
💡 JDK7引入了try-with-resources语法,它可以自动关闭实现了AutoCloseable接口的资源,无需手动在finally块中关闭。
这种写法更简洁,代码可读性更高,是目前推荐的写法。
示例代码:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class TryWithResourcesDemo {
public static void main(String[] args) {
// 将流对象声明在try的括号中,自动关闭
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
String line;
// 按行读取文本文件
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
✅ 核心结论:JDK7及以上版本优先使用try-with-resources语法,简化资源释放代码。
1.5 实战案例:文件夹批量复制工具
1.5.1 需求分析
💡 实现一个工具类,能够复制指定文件夹下的所有文件和子文件夹,包括各种类型的文件(文本、图片、视频等)。
要求:
- 支持大文件复制,使用缓冲流提升效率
- 自动创建目标文件夹,避免路径不存在异常
- 处理复制过程中的IO异常,给出友好提示
1.5.2 代码实现
import java.io.*;
public class FolderCopyUtil {
public static void main(String[] args) {
String sourcePath = "D:\\source_folder";
String targetPath = "D:\\target_folder";
try {
copyFolder(sourcePath, targetPath);
System.out.println("✅ 文件夹复制成功!");
} catch (IOException e) {
System.out.println("❌ 文件夹复制失败:" + e.getMessage());
e.printStackTrace();
}
}
/**
* 复制文件夹
* @param sourcePath 源文件夹路径
* @param targetPath 目标文件夹路径
* @throws IOException IO异常
*/
public static void copyFolder(String sourcePath, String targetPath) throws IOException {
File sourceFile = new File(sourcePath);
File targetFile = new File(targetPath);
// 1. 如果源文件不是文件夹,直接复制文件
if (!sourceFile.isDirectory()) {
copyFile(sourceFile, targetFile);
return;
}
// 2. 创建目标文件夹
if (!targetFile.exists()) {
boolean mkdirsSuccess = targetFile.mkdirs();
if (!mkdirsSuccess) {
throw new IOException("创建目标文件夹失败:" + targetPath);
}
}
// 3. 获取源文件夹下的所有文件和子文件夹
File[] files = sourceFile.listFiles();
if (files == null) {
return;
}
// 4. 循环复制每个文件和子文件夹
for (File file : files) {
String newSourcePath = file.getAbsolutePath();
String newTargetPath = targetPath + File.separator + file.getName();
copyFolder(newSourcePath, newTargetPath);
}
}
/**
* 复制单个文件
* @param sourceFile 源文件
* @param targetFile 目标文件
* @throws IOException IO异常
*/
public static void copyFile(File sourceFile, File targetFile) throws IOException {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetFile))) {
byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
bos.flush();
}
}
}
}
1.5.3 案例测试与总结
① 📝 创建一个测试文件夹,包含文本、图片、视频等多种类型的文件和子文件夹。
② 📝 运行上述代码,指定源文件夹和目标文件夹路径。
③ 📝 检查目标文件夹,确认所有文件和子文件夹都被成功复制。
✅ 案例总结:这个工具类结合了字节流、缓冲流的核心知识,是实际开发中非常实用的功能。
通过这个案例,我们可以掌握IO流的嵌套使用和文件夹递归遍历的技巧。
1.6 本章总结
- 字节流处理所有类型文件,字符流专门处理文本文件,根据场景选择合适的流。
- 缓冲流能大幅提升IO效率,处理大文件时必须使用缓冲流。
- 转换流可以解决文件编码乱码问题,通过指定编码格式实现正确读写。
- JDK7及以上版本优先使用
try-with-resources语法,自动释放IO资源。 - 文件夹复制案例综合运用了IO流的核心知识,是提升实战能力的重要练习。
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/xcLeigh/article/details/157511629



