深入理解 Java IO 流 —— 从入门到实战
在 Java 开发中,IO 流(Input/Output Stream) 是最常用、最基础的技术之一。无论是文件操作、网络传输,还是日志记录与数据持久化,都离不开 IO 流的支持。本文将带你系统学习 Java IO 流,涵盖 字节流、字符流、缓冲流、转换流、对象流、标准流、打印流 等内容,并结合大量案例,帮助你在实际开发中灵活应用。
一、IO 流的基本概念
在计算机系统中,所有的数据都是以 二进制(0 和 1) 的形式存储的。IO 流是一种抽象的概念,用于描述数据在 程序与设备(文件、内存、网络、控制台等)之间的传输方式。
1.1 流的方向
输入流(Input Stream):数据从设备流入程序,例如从文件读取数据。
输出流(Output Stream):数据从程序流向设备,例如向文件写入数据。
记忆方法:始终以 程序为参照物,从程序角度判断方向。
1.2 按数据单位分类
字节流(byte):以字节为单位(1 byte = 8 bit),适合处理任意文件(文本、图片、视频、音频等)。
字符流(char):以字符为单位,专门用于处理文本文件,支持字符集编码。
1.3 四大抽象基类
在 Java 中,所有 IO 流都派生自以下四个抽象基类:
InputStream
—— 字节输入流的父类OutputStream
—— 字节输出流的父类Reader
—— 字符输入流的父类Writer
—— 字符输出流的父类
所有具体流类都继承自它们之一。
二、字节流详解
2.1 FileInputStream —— 文件字节输入流
用于从文件中读取字节数据。
InputStream is = new FileInputStream("D:/test/a.txt");
int data;
while ((data = is.read()) != -1) {System.out.print((char) data);
}
is.close();
常用方法:
int read()
:读取单个字节,返回值为字节数据或 -1(文件结束)。int read(byte[] b)
:读取多个字节到数组,返回实际读取长度。int read(byte[] b, int off, int len)
:读取部分字节到数组指定位置。
2.2 FileOutputStream —— 文件字节输出流
用于向文件中写入字节数据。
OutputStream os = new FileOutputStream("D:/test/b.txt");
os.write("Hello IO".getBytes());
os.close();
常用方法:
void write(int b)
:写入单个字节。void write(byte[] b)
:写入整个字节数组。void write(byte[] b, int off, int len)
:写入数组部分内容。
⚠ 注意:默认会覆盖原文件内容,可以使用 追加模式:
OutputStream os = new FileOutputStream("D:/test/b.txt", true);
2.3 案例:文件复制
InputStream is = new FileInputStream("src/a.txt");
OutputStream os = new FileOutputStream("src/b.txt");byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {os.write(buffer, 0, len);
}os.close();
is.close();
三、字符流详解
字符流适用于文本文件,支持编码和解码。
3.1 FileReader —— 文件字符输入流
Reader reader = new FileReader("src/a.txt");
int ch;
while ((ch = reader.read()) != -1) {System.out.print((char) ch);
}
reader.close();
3.2 FileWriter —— 文件字符输出流
Writer writer = new FileWriter("src/b.txt");
writer.write("你好,世界!\n");
writer.close();
⚠ 注意:字符流不能操作非文本文件(如图片、视频),否则会乱码或损坏文件。
3.3 案例:文本文件拷贝
Reader reader = new FileReader("src/a.txt");
Writer writer = new FileWriter("src/b.txt", true); // 追加模式char[] buffer = new char[1024];
int len;
while ((len = reader.read(buffer)) != -1) {writer.write(buffer, 0, len);
}writer.close();
reader.close();
四、缓冲流
缓冲流是一种 增强流,通过 缓冲区机制 提升读写效率。
4.1 缓冲字节流
BufferedInputStream
BufferedOutputStream
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/a.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/b.jpg"));byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {bos.write(buffer, 0, len);
}bos.close();
bis.close();
4.2 缓冲字符流
BufferedReader
—— 新增readLine()
方法,可逐行读取。BufferedWriter
—— 新增newLine()
方法,跨平台换行。
BufferedReader br = new BufferedReader(new FileReader("src/a.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("src/b.txt"));String line;
while ((line = br.readLine()) != null) {bw.write(line);bw.newLine();
}bw.close();
br.close();
五、转换流
5.1 InputStreamReader 与 OutputStreamWriter
作用:字节流和字符流之间的桥梁。
解决编码问题(UTF-8 / GBK)。
Reader reader = new InputStreamReader(new FileInputStream("src/gbk.txt"), "GBK");
int ch;
while ((ch = reader.read()) != -1) {System.out.print((char) ch);
}
reader.close();
Writer writer = new OutputStreamWriter(new FileOutputStream("src/utf8.txt"), "UTF-8");
writer.write("你好,世界!");
writer.close();
六、标准流
System.in
—— 标准输入(键盘)。System.out
—— 标准输出(控制台)。System.err
—— 标准错误输出。
案例:重定向标准输出
PrintStream ps = new PrintStream("log.txt");
System.setOut(ps);
System.out.println("日志内容");
七、对象流
用于对象序列化与反序列化。
7.1 ObjectOutputStream
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("data.obj"));
oos.writeObject(new Person("张三", 20));
oos.close();
7.2 ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data.obj"));
Person p = (Person) ois.readObject();
ois.close();
⚠ 注意:
对象必须实现
Serializable
接口。transient
修饰的属性不会被序列化。
八、打印流
8.1 PrintStream
PrintStream ps = new PrintStream("out.txt");
ps.println("Hello PrintStream");
ps.printf("姓名: %s, 年龄: %d", "张三", 20);
ps.close();
8.2 PrintWriter
支持字符编码设置,常用于 Web 开发。
PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream("out.txt"), "UTF-8"));
pw.println("你好,世界");
pw.close();
九、实战应用场景
文件拷贝工具类:封装字节流操作,支持大文件复制。
日志系统:使用
PrintWriter
结合BufferedWriter
实现高效日志写入。配置文件读取:使用
BufferedReader
逐行解析配置。对象持久化:使用
ObjectOutputStream
将对象保存到磁盘。
十、总结与面试考点
在学习和掌握了 IO 流的概念、分类以及常用的 API 之后,我们来总结一下核心要点,并结合常见的面试问题进行详细分析。
10.1 字节流与字符流的区别
字节流:以字节为单位(8 位),几乎可以操作所有类型的文件,包括文本、图片、音频、视频。
字符流:以字符为单位(16 位),只适合处理文本数据,更加关注字符编码(UTF-8、GBK 等)。
面试陷阱:为什么图片或音频必须用字节流?因为它们不是文本文件,字符流会尝试编码/解码,导致文件损坏。
10.2 输入流与输出流的区别
输入流:从外部数据源(文件、网络、内存)读取数据到程序中。
输出流:将数据从程序写出到外部目标(文件、网络、内存)。
面试常问:如果要实现文件复制,至少需要哪两种流?答案是输入流和输出流配合使用。
10.3 节点流与处理流(增强流)
节点流:直接与数据源或目的地交互,例如 FileInputStream、FileReader。
处理流(增强流):对节点流进行包装,增加功能或提高性能,例如 BufferedInputStream、InputStreamReader。
面试考点:为什么要使用缓冲流?答:减少磁盘 IO 次数,提升效率。
10.4 常见流的应用场景
字节流:复制二进制文件(图片、视频、音频)。
字符流:读写文本文件。
缓冲流:高效读写大文件,适合日志系统。
转换流:解决编码问题(GBK ↔ UTF-8)。
对象流:实现对象序列化与反序列化。
打印流:日志打印、格式化输出。
10.5 异常处理与资源释放
传统方式:try-catch-finally 手动关闭流。
JDK 7+:try-with-resources 自动关闭流,更加简洁安全。
面试延伸:为什么必须关闭流?答:流会占用操作系统资源(文件句柄、内存等),不关闭会造成资源泄露。
10.6 编码问题与解决方案
不同系统的默认编码可能不同,可能导致乱码。
使用转换流(InputStreamReader/OutputStreamWriter)指定编码格式,避免乱码。
面试延伸:为什么 Windows 下用 FileReader 读取 UTF-8 文件可能乱码?答:因为 FileReader 使用系统默认编码(Windows 默认 GBK),与文件实际编码(UTF-8)不一致。
10.7 对象序列化与反序列化
序列化:将对象转为字节流,写入文件或通过网络传输。
反序列化:将字节流还原为对象。
必须实现
Serializable
接口。关键字:
transient
修饰的变量不会被序列化。面试考点:如果对象中有不可序列化的成员怎么办?答:使用
transient
忽略,或让其也实现 Serializable。
10.8 高级面试题示例
问题:如何在 Java 中实现文件的高效复制? 答案:使用字节缓冲流(BufferedInputStream/BufferedOutputStream),避免逐字节操作。
问题:如何避免 IO 流中出现内存泄漏? 答案:始终关闭流,推荐使用 JDK7+ 的 try-with-resources。
问题:字节流和字符流能否互换? 答案:不能直接互换,但可以通过转换流(InputStreamReader、OutputStreamWriter)实现。
问题:为什么 PrintWriter 在 Web 开发中更常用? 答案:因为 PrintWriter 支持字符编码设置,更适合输出文本数据(如 HTML、JSON)。
结语
Java IO 流体系庞大而灵活,是 Java 基础中的重点。掌握 IO 流不仅能应对常见的文件读写,还能为网络编程、多线程日志、持久化存储等提供坚实基础。希望本文的系统总结与案例讲解,能帮助你在学习与开发中得心应手,也能在面试中脱颖而出。