一、IO 流的核心概念
1. 什么是 IO?
IO 即 Input/Output(输入 / 输出),是 Java 程序和外部设备(文件、网络、控制台)之间数据传输的通道。
- 输入流(Input):数据从外部设备 → 程序,用来读数据。
- 输出流(Output):数据从程序 → 外部设备,用来写数据。
2. 核心特性
- 单向性:流是单向的,输入流只能读、输出流只能写。
- 顺序性:数据按顺序读写,像水管里的水流,不能随机跳着读(随机访问文件流除外)。
- 资源性 :流会占用系统资源,使用完必须关闭(
close()),否则会造成资源泄漏。
二、IO 流的分类体系
Java IO 流有三种主流分类方式,掌握这个分类就掌握了 70% 的 IO 知识。
表格
| 分类维度 | 类别 | 特点 | 代表类 |
|---|---|---|---|
| 按数据单位 | 字节流 | 以字节(byte)为单位,可处理所有类型数据(文本 / 图片 / 视频) | InputStream、OutputStream |
| 字符流 | 以字符(char)为单位,仅处理文本数据,自动适配编码 | Reader、Writer |
|
| 按流向 | 输入流 | 读数据,从外部到程序 | InputStream、Reader |
| 输出流 | 写数据,从程序到外部 | OutputStream、Writer |
|
| 按功能 | 节点流 | 直接和数据源相连,基础的读写流 | FileInputStream、FileReader |
| 处理流(包装流) | 包装节点流,增强读写功能(缓冲、转换、对象序列化) | BufferedReader、ObjectOutputStream |
三、核心类详解(必背)
1. 字节流(InputStream / OutputStream)
核心抽象类
InputStream:所有字节输入流的父类,核心方法:read()(读一个字节 / 字节数组)、close()。OutputStream:所有字节输出流的父类,核心方法:write()(写一个字节 / 字节数组)、flush()(刷新缓冲区)、close()。
常用子类
-
**文件字节流:
FileInputStream/FileOutputStream**直接读写文件,是最基础的节点流,适合处理二进制文件。java// 复制文件(字节流实现,支持所有类型文件) public static void copyFile(String srcPath, String destPath) throws IOException { try (InputStream in = new FileInputStream(srcPath); OutputStream out = new FileOutputStream(destPath)) { byte[] buffer = new byte[1024]; // 缓冲区,一次读1KB int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } } } -
**缓冲字节流:
BufferedInputStream/BufferedOutputStream**包装节点流,自带缓冲区,减少 IO 次数,大幅提升读写效率。java// 带缓冲的文件复制,效率比直接用FileInputStream高很多 public static void copyFileWithBuffer(String srcPath, String destPath) throws IOException { try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcPath)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destPath))) { byte[] buffer = new byte[1024]; int len; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); } bos.flush(); // 强制刷新缓冲区,确保数据全部写入文件 } } -
对象流:
ObjectInputStream/ObjectOutputStream实现对象的序列化 / 反序列化,把对象写入文件或网络,需要对象实现Serializable接口。java// 序列化对象到文件 public static void writeObject(User user, String path) throws IOException { try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path))) { oos.writeObject(user); } } // 从文件反序列化对象 public static User readObject(String path) throws IOException, ClassNotFoundException { try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path))) { return (User) ois.readObject(); } } // 序列化的对象必须实现Serializable接口 static class User implements Serializable { private static final long serialVersionUID = 1L; // 序列化版本号,防止兼容性问题 private String username; private int age; // getter/setter/构造器省略 }
2. 字符流(Reader / Writer)
核心抽象类
Reader:所有字符输入流的父类,核心方法:read()(读一个字符 / 字符数组 / 字符串)、close()。Writer:所有字符输出流的父类,核心方法:write()(写字符 / 字符数组 / 字符串)、flush()、close()。
常用子类
-
**文件字符流:
FileReader/FileWriter**直接读写文本文件,按字符处理,自动适配平台默认编码,不适合处理非文本文件。java// 读取文本文件 public static void readTextFile(String path) throws IOException { try (Reader reader = new FileReader(path)) { char[] buffer = new char[1024]; int len; while ((len = reader.read(buffer)) != -1) { System.out.print(new String(buffer, 0, len)); } } } // 写入文本文件 public static void writeTextFile(String content, String path) throws IOException { try (Writer writer = new FileWriter(path, true)) { // true表示追加模式 writer.write(content); } } -
**转换流:
InputStreamReader/OutputStreamWriter**字节流和字符流的桥梁,可指定编码格式,解决乱码问题(比如读取 GBK 编码的文件)。java// 读取GBK编码的文本文件,避免乱码 public static void readGBKFile(String path) throws IOException { // 把字节流转换成字符流,指定编码为GBK try (Reader reader = new InputStreamReader(new FileInputStream(path), "GBK")) { char[] buffer = new char[1024]; int len; while ((len = reader.read(buffer)) != -1) { System.out.print(new String(buffer, 0, len)); } } } -
**缓冲字符流:
BufferedReader/BufferedWriter**包装字符流,自带缓冲区,支持按行读写,是文本文件处理最常用的类。java// 按行读取文本文件,适合处理大文本 public static void readFileByLine(String path) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader(path))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } } // 按行写入文本文件 public static void writeFileByLine(List<String> lines, String path) throws IOException { try (BufferedWriter bw = new BufferedWriter(new FileWriter(path))) { for (String line : lines) { bw.write(line); bw.newLine(); // 写入换行符,适配不同系统 } } }
3. 打印流(PrintStream / PrintWriter)
-
特点:自动刷新、格式化输出、不会抛出 IO 异常(异常会被捕获),我们常用的
System.out就是PrintStream类型。 -
常用场景:控制台输出、日志打印、文件格式化写入。
// 用PrintWriter写入格式化数据
public static void printToFile(String path) throws IOException {
try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(path)))) {
pw.println("用户信息:");
pw.printf("用户名:%s,年龄:%d%n", "张三", 18); // 格式化输出
pw.println("结束");
}
}
四、经典场景与避坑指南
1. 流的关闭:必须使用 try-with-resources
Java 7 之后推荐使用 try-with-resources 语法,自动关闭流,不用手动写 finally,避免资源泄漏。
java
// 正确写法:try-with-resources自动关闭流
try (InputStream in = new FileInputStream("a.txt");
OutputStream out = new FileOutputStream("b.txt")) {
// 读写操作
}
// 不用手动写close(),JVM会自动关闭流
2. 缓冲区问题:必须 flush()
缓冲流的数据会先写到内存缓冲区,close() 会自动刷新,但如果流还没关闭,必须手动调用 flush() 才能把数据写入文件,否则会出现数据丢失。
3. 乱码问题:必须指定编码
FileReader/FileWriter默认使用平台编码(Windows 是 GBK,Linux 是 UTF-8),跨平台读写容易乱码。- 解决:用
InputStreamReader/OutputStreamWriter显式指定编码,或者使用Files.readAllLines(path, StandardCharsets.UTF_8)。
4. 序列化注意事项
- 序列化的类必须实现
Serializable接口,否则会抛出NotSerializableException。 - 建议显式定义
serialVersionUID,避免类修改后反序列化失败。 transient修饰的字段不会被序列化,适合临时字段或敏感数据。
5. 大文件处理:必须用缓冲流
直接用 FileInputStream 读写大文件,会频繁触发 IO 操作,效率极低,必须用 BufferedInputStream/BufferedOutputStream 包装,或者用 Files.copy()。
五、NIO 补充(进阶)
Java 1.4 引入的 NIO(New IO)是同步非阻塞 IO,核心是通道(Channel)、缓冲区(Buffer)、选择器(Selector),比传统 IO 效率更高,适合高并发网络编程。
- 传统 IO:面向流、阻塞、单线程处理一个连接。
- NIO:面向缓冲区、非阻塞、单线程处理多个连接。
java
// NIO文件复制示例
public static void copyFileWithNIO(String srcPath, String destPath) throws IOException {
try (FileChannel inChannel = new FileInputStream(srcPath).getChannel();
FileChannel outChannel = new FileOutputStream(destPath).getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (inChannel.read(buffer) != -1) {
buffer.flip(); // 切换为读模式
outChannel.write(buffer);
buffer.clear(); // 清空缓冲区,切换为写模式
}
}
}
补充:
| 模型 | 全称 | 阻塞? | 线程模式 | 核心特点 |
|---|---|---|---|---|
| BIO | 阻塞 IO | 阻塞 | 1 线程 1 连接 | 排队死等,低效 |
| NIO | 同步非阻塞 IO | 非阻塞 | 1 线程多连接 | 轮询多路复用 |
| AIO | 异步非阻塞 IO | 异步 | 回调通知 | 系统干完主动通知 |
六、总结:怎么选择流?
| 场景 | 推荐流 | 理由 |
|---|---|---|
| 处理文本文件 | BufferedReader/BufferedWriter |
按行读写,效率高,支持编码 |
| 处理二进制文件(图片 / 视频) | BufferedInputStream/BufferedOutputStream |
字节流处理所有数据类型,缓冲提升效率 |
| 对象序列化 | ObjectOutputStream/ObjectInputStream |
直接读写对象,方便持久化 |
| 解决乱码 | InputStreamReader/OutputStreamWriter |
可指定编码,适配不同文本文件 |
| 高并发网络编程 | NIO 通道 / 缓冲区 | 非阻塞模型,支持多连接 |