IO 流体系总览 | JavaSE
IO 流体系总览
一、学习目标
学完本章,你应该能够:
- 能够解释 IO 中 Input 和 Output 的方向是相对于 Java 程序/内存定义的。
- 能够解释什么是流(Stream),以及为什么 IO 可以抽象成连续的数据流动。
- 能够从“数据方向”和“数据单位”两个维度对 Java IO 进行分类。
- 能够闭卷写出 Java IO 四大抽象基类:
InputStreamOutputStreamReaderWriter
- 能够区分字节流与字符流,并判断文本、图片、视频、压缩包等数据应该优先使用哪类流。
- 能够建立
FileInputStream/FileOutputStream/FileReader/FileWriter的基本继承关系。 - 能够理解缓冲流、转换流、打印流、数据流分别在基础流之上解决什么问题。
- 能够解释为什么
InputStreamReader属于字符输入体系,而PrintStream本质属于字节输出体系。 - 能够画出整个 GROUP 07 后半部分的 IO 学习路线。
二、核心知识
2.1 IO 是什么
IO:
Input
+
Output
即:
输入(Input)与输出(Output)。
Java 程序经常需要和外部世界交换数据:
文件
网络
内存
设备
其他程序
例如:
读取 Markdown 文件
保存日志
上传头像
下载图片
读取配置
复制视频
网络通信
这些本质都属于:
数据输入 / 输出
2.2 什么是 Input
Input:
数据从外部进入 Java 程序。
以 JVM/程序内存为观察中心:
外部数据源
│
│ Input
▼
Java 程序
例如:
磁盘文件
↓
Java 内存
属于:
Input
也就是:
读。
2.3 什么是 Output
Output:
数据从 Java 程序流向外部。
Java 程序
│
│ Output
▼
外部目标
例如:
Java 内存
↓
磁盘文件
属于:
Output
也就是:
写。
2.4 Input / Output 到底相对于谁
这是 IO 最重要的方向问题。
不是相对于:
文件
而是相对于:
当前 Java 程序。
因此:
文件
→
Java
叫:
输入
而:
Java
→
文件
叫:
输出
记忆方法:
以 Java 程序为中心:
进来
=
Input
出去
=
Output
2.5 为什么叫“流”
Stream 可以形象理解成:
水流
数据不是凭空瞬移:
源
↓
一个个数据单位不断流动
↓
目标
例如读取文件:
文件
│
│ byte byte byte byte ...
▼
Java
输出文件:
Java
│
│ byte byte byte byte ...
▼
文件
所以:
流(Stream)是一种连续传输数据的抽象。
2.6 IO 的第一个分类维度:方向
按照:
数据流动方向
可以分成:
输入流
Output? ×
Input
→ 输入流
Output
→ 输出流
即:
| 方向 | 作用 | | ------ | ---------------------- | | 输入流 | 从外部读取数据进入程序 | | 输出流 | 从程序写出数据到外部 |
2.7 IO 的第二个分类维度:数据单位
按照主要处理的数据单位:
字节流
字符流
2.8 什么是字节流
字节流:
以
byte为基本数据单位处理 IO。
核心抽象类:
InputStream
OutputStream
因此:
字节输入流
=
InputStream
字节输出流
=
OutputStream
2.9 字节流可以处理什么
计算机所有文件最终都是:
byte
因此字节流可以处理:
文本
图片
音频
视频
PDF
ZIP
JAR
EXE
……
也就是:
原则上所有类型的数据。
特别是:
图片
视频
压缩包
可执行文件
这类二进制数据应使用:
字节流
而不能随意按字符解码。
2.10 什么是字符流
字符流:
面向字符数据进行读写。
核心抽象类:
Reader
Writer
因此:
字符输入流
=
Reader
字符输出流
=
Writer
字符流主要服务:
文本数据。
例如:
.txt
.java
.md
.json
.xml
.csv
只要这些文件:
本质上是文本内容
就可以考虑字符流。
2.11 为什么字符流主要处理文本
图片:
PNG / JPEG
虽然底层也是:
byte[]
但这些 byte 并不是:
某种字符编码得到的普通文本。
如果强行:
byte[]
↓
字符集解码
↓
char
可能破坏原数据。
所以:
二进制文件
→ 字节流
而:
文本文件
→ 字节流也能处理
→ 但字符流更符合文本语义
2.12 两个分类维度组合
现在:
方向
×
数据单位
组合起来:
| | 输入 | 输出 |
| -------- | ------------- | -------------- |
| 字节 | InputStream | OutputStream |
| 字符 | Reader | Writer |
这就是 Java IO 最核心的:
四大抽象基类。
必须闭卷记住:
Java IO
┌──────┴──────┐
字节流 字符流
│ │
┌───────┴───────┐ ┌────┴─────┐
输入 输出 输入 输出
│ │ │ │
InputStream OutputStream Reader Writer
2.13 四大抽象基类
InputStream
java.io.InputStream
定位:
所有字节输入流的重要抽象父类。
方向:
外部
↓
Java
单位:
byte
OutputStream
java.io.OutputStream
定位:
所有字节输出流的重要抽象父类。
方向:
Java
↓
外部
单位:
byte
Reader
java.io.Reader
定位:
字符输入流抽象父类。
方向:
外部
↓
Java
数据语义:
character
Writer
java.io.Writer
定位:
字符输出流抽象父类。
方向:
Java
↓
外部
数据语义:
character
三、使用方法
3.1 文件 IO 的四个基础实现
当数据源或目标是:
File
基础实现类:
字节:
InputStream
↑
FileInputStream
OutputStream
↑
FileOutputStream
字符:
Reader
↑
FileReader
Writer
↑
FileWriter
因此:
| 需求 | 基础流 |
| ------------------ | ------------------ |
| 从文件读取字节 | FileInputStream |
| 向文件写字节 | FileOutputStream |
| 从文本文件读取字符 | FileReader |
| 向文本文件写字符 | FileWriter |
具体 API 会分别放到:
07-08 字节输入流与字节输出流
07-09 字符输入流与字符输出流
3.2 如何选择字节流还是字符流
最简单判断:
数据是不是纯文本?
如果:
不是
例如:
.jpg
.png
.mp3
.mp4
.zip
.pdf
.class
优先:
字节流
如果:
是文本
例如:
.txt
.java
.md
.json
.xml
通常:
字符流
更加自然。
3.3 不要按照文件后缀机械判断
注意:
.txt
通常是文本。
但真正判断依据不是:
扩展名
而是:
数据内容是否按照字符编码表示文本。
例如某人把:
JPEG 二进制内容
改名为:
photo.txt
它依然不是文本文件。
所以:
后缀
只是习惯。
真正依据:
数据格式
3.4 为什么文本也可以使用字节流
因为文本文件最终也是:
byte[]
所以:
FileInputStream
当然可以读取:
hello.txt
只是读取出来首先是:
byte
如果想得到:
字符
就需要:
正确解码
这正好连接上一章:
byte[]
↓
Charset
↓
String
因此:
字节流
负责:
运输 byte
字符流则进一步帮助:
按照字符语义处理文本
四、原理与进阶
4.1 Java IO 不只有文件
原课程以:
读写文件
为主线,这是 JavaSE 入门最常见场景。
但从 API 设计看:
Stream
并不等于:
File
InputStream 的数据源可以是:
文件
内存
网络
其他流
OutputStream 的目标也可以是:
文件
内存
网络
其他目的地
所以更准确:
File IO 是 Java IO 的一种重要场景,而不是 IO 的全部。
4.2 数据源与数据汇
可以建立两个术语:
Source
数据源
例如:
文件
网络连接
内存 byte[]
以及:
Sink
数据目标
例如:
文件
网络
内存
于是:
Source
│
│ Input
▼
Java
│
│ Output
▼
Sink
这就是 IO 的统一模型。
4.3 基础流和功能增强流
为了学习方便,可以把很多 IO 类理解成两类。
直接连接数据源/目标
例如:
FileInputStream
FileOutputStream
FileReader
FileWriter
可以理解为:
基础数据通道。
在已有流上增加能力
例如:
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
增加:
缓冲能力
还有:
DataInputStream
DataOutputStream
增加:
按 Java 基本数据类型读写
以及:
PrintStream
PrintWriter
增加:
方便打印各种数据
因此 IO 中经常出现:
基础流
↓
包装 / 增强
↓
获得更多能力
这种组合思想。
4.4 Java IO 的正确体系图
按照 JDK 21 类型层级,可建立如下主线:
java.io
字节输入
InputStream
├── FileInputStream
├── BufferedInputStream
├── DataInputStream
└── ...
字节输出
OutputStream
├── FileOutputStream
├── BufferedOutputStream
├── DataOutputStream
├── PrintStream
└── ...
字符输入
Reader
├── InputStreamReader
│ └── FileReader
├── BufferedReader
└── ...
字符输出
Writer
├── OutputStreamWriter
│ └── FileWriter
├── BufferedWriter
├── PrintWriter
└── ...
注意:
这张图只展示本课程相关的主要类,不是
java.io全部类。
4.5 InputStreamReader 到底属于哪边
InputStreamReader:
继承 Reader
所以它属于:
字符输入流体系
但是它的特殊职责是:
InputStream
提供 byte
↓
InputStreamReader
使用 Charset 解码
↓
提供 character
因此它被称为:
字节流到字符流的桥梁。
这正是:
07-11 转换流
要系统学习的内容。
4.6 OutputStreamWriter
方向正好相反:
character
↓
OutputStreamWriter
使用 Charset 编码
↓
byte
↓
OutputStream
因此:
OutputStreamWriter是字符流到字节流的桥梁。
4.7 字符流底层为什么仍然离不开字节
因为:
磁盘
网络
最终通常保存或传输:
byte
所以字符输入的真正过程可以理解为:
文件 byte
↓
读取
↓
按照 Charset 解码
↓
character
↓
Java
字符输出:
Java character
↓
按照 Charset 编码
↓
byte
↓
文件
所以:
07-06 编码解码
并不是和 IO 无关的知识。
恰恰相反:
它是理解字符 IO 的理论基础。
4.8 PrintStream 到底属于字节流还是字符流
虽然名字:
PrintStream
经常用于:
System.out
输出文字,
但其继承体系是:
OutputStream
↑
FilterOutputStream
↑
PrintStream
所以:
PrintStream本质属于字节输出流体系。
而:
PrintWriter
继承:
Writer
属于:
字符输出体系。
这两个名字非常容易混淆。
4.9 IO 为什么存在如此多的类
初学者看到:
InputStream
FileInputStream
BufferedInputStream
DataInputStream
Reader
FileReader
BufferedReader
InputStreamReader
OutputStream
FileOutputStream
BufferedOutputStream
PrintStream
Writer
FileWriter
BufferedWriter
PrintWriter
容易产生:
“Java 为什么搞这么复杂?”
实际上每个类解决不同维度的问题:
数据方向:
读 / 写
数据单位:
byte / character
数据来源:
File / memory / network
功能:
buffer / convert / print / data
可以想象成:
方向
×
数据单位
×
数据来源
×
增强功能
组合出不同的 IO 类。
所以真正应该掌握:
分类坐标系。
而不是死背几十个类。
五、实践应用
5.1 图片复制应该选什么
图片:
avatar.png
不是文本。
应该:
字节输入流
+
字节输出流
即基础思路:
PNG File
↓
FileInputStream
↓
byte
↓
FileOutputStream
↓
目标 PNG File
5.2 Markdown 文件阅读
例如:
JavaSE.md
属于文本。
如果目的是:
按字符
按行
处理文本
通常字符流更加方便:
Reader
体系。
5.3 视频文件为什么不能使用 Reader
视频:
video.mp4
底层 byte 并不是:
UTF-8 字符序列
如果错误地:
byte
↓
Charset 解码
↓
char
再写回:
原始二进制结构可能被破坏
因此:
二进制文件
=
字节流
是必须建立的基本原则。
5.4 星雨笔录中的 IO 场景
例如:
教程 Markdown
博客正文文件
日志
图片
头像
附件
备份文件
可以分类:
Markdown / 日志
→ 文本
→ 字符流较自然
PNG / JPG / ZIP / PDF
→ 二进制
→ 字节流
以后如果迁移到:
OSS
网络传输
HTTP
底层仍然离不开:
byte
的输入输出模型。
5.5 GROUP 07 后半部分完整路线
从现在开始,后面章节其实就是不断给 IO 坐标系增加能力:
07-07
IO 总览
↓
07-08
字节输入 / 输出
↓
07-09
字符输入 / 输出
↓
07-10
缓冲
↓
07-11
字节 ↔ 字符转换
↓
07-12
打印 / 基本数据类型
↓
07-13
资源释放
↓
07-14
Commons IO + 综合应用
所以从本章开始:
不要再孤立地背每一个类。
每遇到一个新 IO 类,都问:
它是输入还是输出?
处理 byte 还是 character?
直接连接什么数据源?
在已有流上增加什么能力?
六、常见问题
6.1 Input 是文件输入到哪里?
不是:
Java → File
而是:
File → Java
以:
Java 程序
为参照物。
6.2 Output 是输出到屏幕吗?
不一定。
Output 只表示:
数据从 Java 程序流出。
目标可能:
文件
网络
内存
控制台
6.3 InputStream 与 Reader 有什么核心区别?
InputStream
→ byte
Reader
→ character
都属于:
Input
但是数据单位和语义不同。
6.4 OutputStream 与 Writer 呢?
OutputStream
→ byte 输出
Writer
→ character 输出
6.5 字节流是不是只能操作图片和视频?
不是。
字节流:
可以操作所有基于字节的数据。
包括:
文本
图片
视频
只是文本如果需要:
按字符语义处理
字符流更方便。
6.6 字符流是不是“比字节流高级”?
不是。
二者解决:
不同的数据语义
图片使用:
Reader
反而是错误选型。
所以不是:
谁高级
而是:
谁适合当前数据
6.7 File 和 IO Stream 有什么关系?
上一阶段:
File
主要解决:
文件在哪里?
文件是什么?
目录里面有什么?
IO Stream:
真正负责数据读写。
因此:
File
≈ 文件系统对象 / 路径
Stream
≈ 数据传输通道
6.8 Reader 是不是直接从磁盘读取 char?
从高层 API 使用角度可以:
把它理解为读取字符
但底层存储最终仍是 byte。
字符流需要:
byte
↓
Charset 解码
↓
character
所以字符 IO 与字符编码密切相关。
6.9 PrintStream 为什么不是字符流?
因为它的继承关系是:
PrintStream
↓
FilterOutputStream
↓
OutputStream
所以:
它是字节输出流体系中的类
只是额外提供非常方便的:
print()
println()
printf()
能力。
七、练习与验收
7.1 知识问答
- IO 分别是什么单词?
- Input / Output 方向相对于谁定义?
- 文件到 Java 是 Input 还是 Output?
- Java 到文件是什么?
- Java IO 可以按照哪两个核心维度分类?
- 四大抽象基类分别是什么?
InputStream处理什么?OutputStream处理什么?Reader处理什么?Writer处理什么?- 字节流可以处理哪些数据?
- 字符流适合什么数据?
- 为什么图片不能随意使用字符流?
- 文本文件为什么也可以使用字节流?
FileInputStream属于哪个体系?FileWriter属于哪个体系?InputStreamReader属于哪个体系?- 为什么
InputStreamReader被称为桥梁? PrintStream属于字节还是字符体系?- IO 为什么会设计这么多不同的类?
7.2 代码阅读
判断每种需求应该优先选择哪个体系:
1. 复制 avatar.jpg
2. 读取 JavaSE.md 的文字内容
3. 复制 movie.mp4
4. 向 log.txt 写日志文字
5. 复制 app.zip
6. 读取 JSON 配置文本
只能先选择:
InputStream / OutputStream
或
Reader / Writer
暂时不用写具体代码。
7.3 手写代码
任务一:画 IO 分类图
闭卷画:
Java IO
↓
字节 / 字符
↓
输入 / 输出
最终必须出现:
InputStream
OutputStream
Reader
Writer
任务二:继承关系
写出:
InputStream
→ FileInputStream
OutputStream
→ FileOutputStream
Reader
→ FileReader
Writer
→ FileWriter
并说明它们分别解决什么需求。
任务三:技术选型
对下面数据做技术选择:
Markdown
PNG
MP4
ZIP
Java 源代码
日志
PDF
CSV
分别说明:
更适合字节流还是字符流?
为什么?
7.4 Debug
同学写出:
读取图片
→ FileReader
复制视频
→ FileWriter
读取 Markdown
→ FileInputStream 一定错误
PrintStream
→ Writer 子类
逐条判断:
正确 / 错误
并说明原因。
7.5 综合训练
画出下面的数据链路:
文本读取
UTF-8 文件
↓
磁盘 byte
↓
读取
↓
UTF-8 解码
↓
character
↓
Java String
文本写出
Java String
↓
character
↓
UTF-8 编码
↓
byte
↓
文件
然后回答:
- 哪一步属于 Input?
- 哪一步属于 Output?
- 哪一步涉及上一章的解码?
- 哪一步涉及上一章的编码?
- 为什么字符流和字符集知识必须放在一起理解?
7.6 本章验收
必须闭卷口述:
IO 是 Input / Output。
方向永远相对于 Java 程序:
外部 → Java
叫 Input。
Java → 外部
叫 Output。
IO 又可以按照数据单位分为:
字节流
字符流。
两个维度组合形成四大体系:
InputStream
字节输入
OutputStream
字节输出
Reader
字符输入
Writer
字符输出。
字节流可以处理所有类型的数据,
特别适合二进制文件。
字符流主要服务文本,
其底层涉及字符编码和解码。
File 主要描述文件系统对象,
IO Stream 才真正负责数据传输。
最终验收:
- [ ] 闭卷写出四大抽象基类。
- [ ] 能判断 Input / Output 方向。
- [ ] 能区分 byte 与 character。
- [ ] 能给图片、视频、文本选择正确流体系。
- [ ] 能画基本继承关系。
- [ ] 能解释 File 与 Stream 的区别。
- [ ] 能解释字符流为什么涉及 Charset。
- [ ] 能识别
InputStreamReader的真实位置。 - [ ] 能识别
PrintStream的真实位置。 - [ ] 已经形成完整 IO 分类坐标系。