Java IO流:从字节到字符的桥梁
1. 引入:为什么需要IO流?
程序运行时,数据都在内存中。但我们需要:
- 把数据永久保存到文件(硬盘)
- 从网络接收数据
- 读取配置文件
IO流就是用来处理数据传输的。I(Input)输入,O(Output)输出。数据像水一样"流动",所以叫流。
1.1 流的分类思维导图
IO流
|
┌────────────────┴────────────────┐
│ │
字节流 字符流
(处理二进制数据) (处理文本数据)
| |
┌────┴────┐ ┌────┴────┐
│ │ │ │
输入流 输出流 输入流 输出流
InputStream OutputStream Reader Writer
2. 字节流:一切的基础
字节流可以处理任何类型的数据(图片、视频、音频、文本),因为它以字节(8位)为单位。
2.1 字节输出流:OutputStream
FileOutputStream:将数据写入文件。
java
// 1. 创建流(指定文件)
FileOutputStream fos = new FileOutputStream("a.txt"); // 覆盖写入
FileOutputStream fos2 = new FileOutputStream("a.txt", true); // 追加写入
// 2. 写入数据
fos.write(97); // 写入一个字节('a')
byte[] bytes = {97, 98, 99};
fos.write(bytes); // 写入字节数组
fos.write(bytes, 1, 2); // 写入从索引1开始的2个字节
// 3. 关闭流(重要!)
fos.close();
2.2 字节输入流:InputStream
FileInputStream:从文件读取数据。
java
FileInputStream fis = new FileInputStream("a.txt");
// 方式1:单个字节读取(效率低)
int b;
while ((b = fis.read()) != -1) { // -1表示文件结束
System.out.print((char) b);
}
// 方式2:批量读取(效率高)
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
System.out.println(new String(buffer, 0, len));
}
fis.close();
2.3 字节流复制文件示例
java
// 文件复制(万能)
try (FileInputStream fis = new FileInputStream("source.jpg");
FileOutputStream fos = new FileOutputStream("dest.jpg")) {
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
3. 字符流:处理文本更高效
字符流以字符(16位)为单位,专门处理文本文件,避免中文乱码。
3.1 字符输出流:Writer
FileWriter:写入文本文件。
java
FileWriter fw = new FileWriter("b.txt");
fw.write('你'); // 写入单个字符
fw.write("你好世界"); // 写入字符串
fw.write("你好Java", 0, 2); // 写入字符串前2个字符
fw.close();
3.2 字符输入流:Reader
FileReader:读取文本文件。
java
FileReader fr = new FileReader("b.txt");
// 单个字符读取
int ch;
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
// 字符数组读取
char[] buffer = new char[1024];
int len;
while ((len = fr.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
fr.close();
3.3 处理中文乱码
FileReader/FileWriter使用默认编码 (IDE或系统编码),跨平台可能乱码。
同一台电脑上的两个程序(编辑器 vs 终端)默认编码不同,导致乱码。
解决方案:使用转换流指定编码。
java
// 指定UTF-8编码读取
InputStreamReader isr = new InputStreamReader(
new FileInputStream("c.txt"), "UTF-8");
// 指定GBK编码写入
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("d.txt"), "GBK");
字符字节流区别:
| 特性 | 字节流 | 字符流 |
|---|---|---|
| 基本单位 | 字节(8 bit) | 字符(16 bit Unicode) |
| 编码处理 | 无,需手动处理 | 自动使用字符集编解码 |
| 适用数据 | 任何类型(二进制 + 文本) | 仅文本 |
| 抽象基类 | InputStream / OutputStream |
Reader / Writer |
| 典型应用 | 图片、视频、音频、网络传输 | 文本文件读写、控制台输入输出 |
4. 缓冲流:提升性能
缓冲流内部维护一个缓冲区,减少实际读写次数,大幅提升效率。
4.1 字节缓冲流
java
// 输入
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("large.jpg"));
// 输出
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("copy.jpg"));
byte[] buffer = new byte[8192];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bis.close();
bos.close();
4.2 字符缓冲流
特有方法:readLine() 和 newLine()。
java
// 读取文本行
BufferedReader br = new BufferedReader(
new FileReader("poem.txt"));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
// 写入文本行(自动换行)
BufferedWriter bw = new BufferedWriter(
new FileWriter("output.txt"));
bw.write("第一行");
bw.newLine(); // 跨平台的换行
bw.write("第二行");
bw.close();
5. 转换流:字节→字符的转换
| 类 | 作用 |
|---|---|
InputStreamReader |
字节输入流 → 字符输入流(指定编码) |
OutputStreamWriter |
字符输出流 → 字节输出流(指定编码) |
java
// 读取GBK编码的文件
InputStreamReader isr = new InputStreamReader(
new FileInputStream("gbk.txt"), "GBK");
// 写入UTF-8编码的文件
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("utf8.txt"), "UTF-8");
6. 对象流:序列化与反序列化
6.1 概念
- 序列化:把对象变为字节序列(保存到文件或网络传输)
- 反序列化:把字节序列恢复为对象
6.2 使用步骤
java
// 类必须实现Serializable接口
class Student implements Serializable {
private static final long serialVersionUID = 1L; // 版本号
private String name;
private transient int age; // transient:不序列化
}
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("student.obj"));
oos.writeObject(new Student("张三", 18));
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("student.obj"));
Student s = (Student) ois.readObject();
ois.close();
6.3 注意事项
| 要点 | 说明 |
|---|---|
Serializable |
标记接口,没有方法 |
serialVersionUID |
版本控制,修改类后能兼容 |
transient |
修饰的字段不序列化 |
| 静态变量 | 不序列化(属于类) |
7. IO流对比总结
| 分类 | 输入流 | 输出流 | 适用场景 |
|---|---|---|---|
| 字节流 | InputStream |
OutputStream |
图片、视频、音频 |
| 字符流 | Reader |
Writer |
文本文件 |
| 缓冲字节流 | BufferedInputStream |
BufferedOutputStream |
大文件字节复制 |
| 缓冲字符流 | BufferedReader |
BufferedWriter |
大文本处理(readLine) |
| 转换流 | InputStreamReader |
OutputStreamWriter |
指定编码读写 |
| 对象流 | ObjectInputStream |
ObjectOutputStream |
对象序列化 |
8. 易错点总结
| 易错点 | 错误原因 | 正确做法 |
|---|---|---|
| 忘记关闭流 | 资源泄漏 | try-with-resources或finally中close |
| 字节流读中文乱码 | 汉字占多个字节 | 用字符流或指定编码 |
| 文件路径错误 | 相对路径基准不明 | 使用绝对路径或ClassLoader |
| read(byte[])返回值忽略 | -1判断错误 | 用len记录实际读取长度 |
| 字符数组转String不使用长度 | 多读空字符 | new String(buffer, 0, len) |
| flush忘记调用 | 数据没真正写入 | 缓冲流调用flush()或关闭流 |