

在 Java 开发中,IO(Input/Output)操作是与外部设备(文件、网络、键盘等)交互的基础。无论是读取配置文件、处理用户输入,还是写入日志数据,都离不开 IO 流。但很多开发者在面对繁多的 IO 类时会感到困惑:InputStream和Reader有什么区别?FileOutputStream和BufferedOutputStream该怎么选?本文将从 IO 流的本质出发,详细解析流的分类逻辑和 Java IO 的体系结构,帮你彻底理清 IO 流的脉络。
一、什么是 IO 流?
简单来说,IO 流就是数据的传输过程,以 "流" 的形式(像水流一样连续)在程序与外部设备之间传递数据。这里的 "外部设备" 可以是文件、数据库、网络连接、键盘、显示器等。
举个生活中的例子:如果把程序比作一个水池,那么从外部设备向程序传输数据(比如读文件)就像 "进水",从程序向外部设备传输数据(比如写文件)就像 "出水",而 "水管" 就是 IO 流。
二、IO 流的分类:从两个核心维度划分
Java 的 IO 流种类繁多,但所有流都可以从数据单位和传输方向两个核心维度进行分类,这是理解 IO 流的基础。
2.1 按数据单位:字节流 vs 字符流
这是最核心的分类方式,决定了流处理数据的基本单位。
字节流(Byte Stream)
- 处理单位:以字节(8 位二进制)为单位处理数据
- 适用场景:所有类型的数据(文本、图片、音频、视频等)
- 核心基类:
InputStream(输入字节流)、OutputStream(输出字节流)
字符流(Character Stream)
- 处理单位:以字符(16 位 Unicode)为单位处理数据
- 适用场景:仅用于处理文本数据(如.txt、.java 文件),会涉及编码转换(如 UTF-8、GBK)
- 核心基类:
Reader(输入字符流)、Writer(输出字符流)
关键区别:字节流是 "原生" 的,直接操作二进制数据;字符流是对字节流的封装,会根据编码表将字节转换为字符(避免中文等字符因编码问题出现乱码)。
比如读取一个包含中文的文本文件:用字节流直接读会得到字节数组,需要手动转字符(可能因编码错误乱码);而字符流会自动处理编码转换,直接得到字符。
2.2 按传输方向:输入流 vs 输出流
传输方向是以程序为参照物定义的:
输入流(Input Stream)
- 数据从外部设备流向程序("读" 操作)
- 核心行为:
read()(读取数据)
输出流(Output Stream)
- 数据从程序流向外部设备("写" 操作)
- 核心行为:
write()(写入数据)
注意:方向是相对程序而言的。比如 "读文件" 是输入流(文件→程序),"写文件" 是输出流(程序→文件)。
2.3 分类逻辑可视化
下面用图直观展示 IO 流的分类逻辑:

通过组合上述两个维度,我们可以得到 4 种基本流类型:
- 输入字节流(
InputStream体系)- 输出字节流(
OutputStream体系)- 输入字符流(
Reader体系)- 输出字符流(
Writer体系)
三、Java IO 体系结构:从基类到具体实现
Java 的 IO 流设计遵循 "基类定义规范,子类实现具体功能" 的原则,所有流都基于 4 个顶级抽象基类展开,形成了清晰的体系结构。
3.1 四大顶级抽象基类
这 4 个类是所有 IO 流的 "根",定义了流的基本行为(但本身不能直接实例化):
| 流类型 | 顶级基类 | 核心方法 | 说明 |
|---|---|---|---|
| 输入字节流 | InputStream | int read()、close() | 读取字节数据 |
| 输出字节流 | OutputStream | void write(int b)、close() | 写入字节数据 |
| 输入字符流 | Reader | int read(char[] cbuf)、close() | 读取字符数据 |
| 输出字符流 | Writer | void write(String str)、close() | 写入字符数据 |
注意:InputStream和Reader的read()方法返回值为int(而非byte或char),当返回-1时表示读取到末尾;所有流使用后必须调用close()释放资源(Java 7 + 可通过 try-with-resources 自动关闭)。
3.2 常用子类及功能
基于四大基类,Java 提供了众多子类以应对不同场景,按功能可分为节点流(直接操作数据源)和处理流(增强节点流功能)。
3.2.1 节点流:直接连接数据源
节点流是 "直接干活" 的流,直接与外部设备(如文件、内存)交互:
| 流类型 | 常用类 | 功能描述 |
|---|---|---|
| 输入字节流 | FileInputStream | 从文件读取字节数据 |
| 输出字节流 | FileOutputStream | 向文件写入字节数据 |
| 输入字符流 | FileReader | 从文件读取字符数据(默认编码) |
| 输出字符流 | FileWriter | 向文件写入字符数据(默认编码) |
| 输入字节流 | ByteArrayInputStream | 从字节数组读取数据(内存操作) |
| 输出字节流 | ByteArrayOutputStream | 向字节数组写入数据(内存操作) |
3.2.2 处理流:增强节点流功能
处理流不能直接连接数据源,需 "包裹" 节点流或其他处理流,提供缓冲、编码转换、对象序列化等增强功能:
| 功能 | 处理流类 | 说明 |
|---|---|---|
| 缓冲功能 | BufferedInputStream、BufferedOutputStream | 字节流缓冲(减少 IO 次数,提高效率) |
| 缓冲功能 | BufferedReader、BufferedWriter | 字符流缓冲(新增readLine()等方法) |
| 编码转换 | InputStreamReader、OutputStreamWriter | 字节流→字符流(指定编码,解决乱码) |
| 对象序列化 | ObjectInputStream、ObjectOutputStream | 读写 Java 对象(需实现Serializable) |
| 打印功能 | PrintStream、PrintWriter | 方便打印数据(如System.out就是PrintStream) |
3.3 体系结构可视化
用图展示 IO 流的核心体系结构(只包含最常用类):

四、如何选择合适的 IO 流?
面对众多 IO 流,选择的核心原则是:根据数据类型和操作场景匹配流的功能。
-
先确定数据类型:
- 非文本数据(图片、视频等)→ 字节流(
InputStream/OutputStream体系) - 文本数据(.txt、.java 等)→ 字符流(
Reader/Writer体系,避免乱码)
- 非文本数据(图片、视频等)→ 字节流(
-
再确定操作方向:
- 读数据→ 输入流(
InputStream/Reader) - 写数据→ 输出流(
OutputStream/Writer)
- 读数据→ 输入流(
-
最后考虑增强需求:
- 需要提高效率→ 加缓冲流(
BufferedXXX) - 需要指定编码→ 用转换流(
InputStreamReader/OutputStreamWriter) - 需要读写对象→ 用对象流(
ObjectInputStream/ObjectOutputStream)
- 需要提高效率→ 加缓冲流(
五、总结
Java IO 流的设计看似复杂,实则有清晰的逻辑:
- 按数据单位分为字节流(处理所有数据)和字符流(处理文本数据)
- 按传输方向分为输入流(读)和输出流(写)
- 体系结构以
InputStream、OutputStream、Reader、Writer为基类,子类按 "节点流直接操作数据,处理流增强功能" 的模式展开
掌握 IO 流的核心是理解 "数据单位" 和 "传输方向" 的分类逻辑,再结合具体场景选择合适的流。实际开发中,缓冲流(提高效率)和转换流(处理编码)是最常用的增强流,建议重点掌握。
希望本文能帮你理清 IO 流的脉络,下次面对 IO 操作时不再迷茫!

788

被折叠的 条评论
为什么被折叠?



