发布于2026-05-27 阅读(0)
扫一扫,手机访问

在Ja va的世界里,文件操作是绕不开的基础。无论是读取配置文件,还是保存用户数据,都离不开“流”这个概念。简单来说,流就是数据流动的通道。上面这张图,就为我们清晰地展示了Ja va中几种核心流类协作完成文件读写的典型流程。
要理清Ja va的流体系,首先得从分类入手。根据功能角色,流主要分为两大类:
FileInputStream 和 FileOutputStream。FileReader 和 FileWriter。BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter。InputStreamReader, OutputStreamWriter,用于在字节和字符之间搭桥。ObjectInputStream, ObjectOutputStream,用于序列化与反序列化对象。了解了分类,接下来看看不同流各自的应用场景和特点,这是选择合适工具的关键。
.docx、.png、.mp4 等,字节流都能处理。.txt、.ja va、.py、.c 等源代码或配置文件。.docx、.xlsx、.pptx 这类文件,虽然里面包含文字,但它们的格式是复杂的二进制结构,并非纯文本文件,因此不能用字符流直接读取其内容。

字节流是文件操作的基石。下面是一个典型的文件复制示例,展示了如何使用 FileInputStream 和 FileOutputStream:
public void copyFileWithFile(String srcPath, String destPath) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//1. 创建File对象,指定源文件和目标文件
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//2. 创建节点流
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//3. 核心读写过程:使用缓冲区循环读取写入
int len;
byte[] buffer = new byte[100];
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭资源,确保释放系统资源
try {
if (fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码逻辑清晰:创建流、循环读写、关闭资源。注意,使用字节数组作为缓冲区(这里是100字节)是标准做法,比单字节读写高效得多。最后在finally块中关闭流是必须的好习惯。

在节点流的基础上套上缓冲流,性能往往能得到立竿见影的提升。下面是使用缓冲流进行文件复制的代码:
public void copyFileWithBuffered(String srcPath, String destPath) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1. 创建File对象
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//2. 先创建节点流,再在其基础上创建缓冲流
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3. 读写过程(与字节流类似,但操作的是缓冲流)
int len;
byte[] buffer = new byte[100];
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭资源:只需关闭外层缓冲流,它会自动关闭内层的节点流
try {
if(bos != null)
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(bis != null)
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
// 内层的fis和fos关闭可以省略,因为关闭bos/bis时会自动处理
// fos.close();
// fis.close();
}
}


关键点在于流的嵌套结构:BufferedInputStream 包装了 FileInputStream。关闭时,只需要关闭最外层的缓冲流即可,这是一种标准的装饰器模式应用。
字符流,如 FileReader 和 FileWriter,是处理文本文件的利器。但更常见的做法是使用转换流 InputStreamReader 和 OutputStreamWriter,因为它们可以显式指定字符集,避免乱码。下面是一个指定编码进行文本文件复制的例子:
public void test() {
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
// 用GBK编码读取源文件
isr = new InputStreamReader(new FileInputStream("康师傅的话.txt"),"gbk");
// 用UTF-8编码写入目标文件
osw = new OutputStreamWriter(new FileOutputStream("C:\Users\shkstart\Desktop\寄语.txt"),"utf-8");
char[] cbuf = new char[1024];
int len;
while ((len = isr.read(cbuf)) != -1) {
osw.write(cbuf, 0, len);
osw.flush(); // 及时刷新缓冲区,确保数据写入
}
System.out.println("文件复制完成");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (isr != null)
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (osw != null)
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
这段代码实现了编码转换:将一个GBK编码的文本文件,读取并转换为UTF-8编码保存。注意,字符流操作的单位是字符数组 char[]。
转换流的核心价值在于处理字符集转换。下面的例子更清晰地展示了如何将一个文件从一种编码转换为另一种编码:
public void test4() {
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
//1. 创建文件对象
File file1 = new File("dbcp_gbk.txt");
File file2 = new File("dbcp_gbk_to_utf8.txt");
//2. 创建流,并指定编码
FileInputStream fis = new FileInputStream(file1);
// 解码:用GBK字符集将字节转换为字符(必须与源文件编码一致)
isr = new InputStreamReader(fis,"GBK");
FileOutputStream fos = new FileOutputStream(file2);
// 编码:用UTF-8字符集将字符转换为字节,存入新文件
osw = new OutputStreamWriter(fos,"utf8");
//3. 读写过程
char[] cBuffer = new char[1024];
int len;
while((len = isr.read(cBuffer)) != -1){
osw.write(cBuffer,0,len);
}
System.out.println("操作完成");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//4. 关闭资源
try {
if(osw != null)
osw.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
if(isr != null)
isr.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
这个过程可以概括为:字节流(文件) -> 解码(InputStreamReader) -> 字符数据 -> 编码(OutputStreamWriter) -> 字节流(新文件)。正确指定编码是解决乱码问题的关键。


对象流让存储和恢复Ja va对象变得简单。首先,需要序列化的类必须实现 ja va.io.Serializable 接口。
package com.atguigu05.objectstream;
import ja va.io.Serializable;
public class Person implements Serializable { //Serializable:属于一个标识接口
String name;
int age;
int id;
Account acct;
// 显式声明序列化版本UID,强烈推荐,用于控制版本兼容性
static final long serialVersionUID = 422334254234L;
// 构造器、getter/setter、toString方法省略...
// ... (为保持内容完整,此处保留原代码结构)
@Override
public String toString() {
return "Person{" +
"name='" + name + ''' +
", age=" + age +
", id=" + id +
", acct=" + acct +
'}';
}
}
class Account implements Serializable{
double balance;
static final long serialVersionUID = 422234L;
// 构造器、toString方法省略...
}
注意 serialVersionUID 的作用。它相当于对象的“版本号”。如果序列化和反序列化时这个值不一致,就会导致 InvalidClassException。显式声明可以避免因类细节(如增加一个方法)改变而导致的意外不兼容。
将对象写入文件:
public void test3(){
ObjectOutputStream oos = null;
try {
File file = new File("object1.dat");
oos = new ObjectOutputStream(new FileOutputStream(file));
//2. 写出对象,即序列化
Person p1 = new Person("Tom",12);
oos.writeObject(p1);
oos.flush(); // 刷新缓冲区
Person p2 = new Person("Jerry",23,1001,new Account(2000));
oos.writeObject(p2);
oos.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if(oos != null)
oos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
从文件中读取并恢复对象:
public void test4() {
ObjectInputStream ois = null;
try {
File file = new File("object1.dat");
ois = new ObjectInputStream(new FileInputStream(file));
//2. 读取对象,即反序列化(需强制类型转换)
Person person = (Person) ois.readObject();
System.out.println(person);
Person person1 = (Person) ois.readObject();
System.out.println(person1);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
try {
if(ois != null)
ois.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
反序列化时,JVM会根据字节流中的类描述信息找到对应的类,并创建对象。如果找不到类,则会抛出 ClassNotFoundException。
至此,我们梳理了Ja va中从基础的字节流、字符流,到提升性能的缓冲流,再到处理编码的转换流,以及用于对象持久化的对象流。理解它们各自的定位和组合方式,是掌握Ja va I/O编程的关键一步。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8