一、流的作用
在 Java 中,流(Stream)是对数据传输过程的抽象。无论是从文件读取数据、向网络发送请求,还是在内存中处理字节序列,程序都可以用统一的「流」模型来描述:数据像水流一样,从一端流向另一端。
流的核心作用可以概括为以下几点:
- 统一数据源与数据目标:文件、内存数组、网络连接、控制台都可以抽象为流,读写代码的形态保持一致。
- 屏蔽底层细节:调用方不需要关心数据是来自磁盘扇区还是网络报文,只需要面向流的接口编程。
- 支持顺序读写与缓冲:流天然适合顺序处理,配合缓冲流可以显著减少系统调用次数,提升 I/O 效率。
- 便于装饰与组合:通过装饰器模式,可以在基础流之上叠加缓冲、字符编码、对象序列化等能力,功能按需拼装。
java
// 同一个「读」的语义,可以作用于不同数据源
InputStream fromFile = new FileInputStream("data.txt");
InputStream fromMemory = new ByteArrayInputStream("hello".getBytes());
InputStream fromNetwork = socket.getInputStream();
正是这种抽象,让 Java 的 I/O 体系具备了良好的扩展性:新增一种数据源,往往只需要实现对应的流类,而不必改动上层业务代码。
二、流的划分
Java 的流体系看似庞杂,但按照几个维度划分后,结构会非常清晰。
1. 按数据单位划分:字节流与字符流
| 维度 | 字节流 | 字符流 |
|---|---|---|
| 顶层抽象类 | InputStream / OutputStream |
Reader / Writer |
| 处理单位 | 字节(8 位) | 字符(16 位,按编码解码) |
| 适用场景 | 图片、音频、压缩包等二进制数据 | 文本文件、配置、日志等字符数据 |
| 典型实现 | FileInputStream、BufferedInputStream |
FileReader、BufferedReader |
字节流是最基础的,字符流本质上是在字节流之上增加了编码转换。处理文本时优先使用字符流,可以避免手动处理编码问题;处理二进制数据时必须使用字节流。
2. 按数据流向划分:输入流与输出流
- 输入流 :从外部数据源读入程序,如
InputStream、Reader。 - 输出流 :从程序写出到外部目标,如
OutputStream、Writer。
这里的「输入输出」是站在程序的角度定义的:数据进入程序叫输入,数据离开程序叫输出。
3. 按功能层次划分:节点流与处理流
- 节点流 :直接连接数据源或目标,如
FileInputStream、FileOutputStream、ByteArrayInputStream。 - 处理流 :包装在其他流之上,提供额外能力,如
BufferedInputStream(缓冲)、DataInputStream(基本类型读写)、ObjectInputStream(对象序列化)、InputStreamReader(字节转字符)。
java
// 节点流 + 处理流的典型组合
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("data.txt"), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
这种分层设计让每种流只关注一件事,通过组合即可满足复杂需求。
4. 按处理方式划分:缓冲流与直接流
缓冲流在内部维护一个字节或字符数组,减少与底层设备的交互次数。对于频繁的小块读写,缓冲带来的性能提升非常明显。常见的有 BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter。
三、标准输入输出流
Java 在 System 类中预定义了三个标准流,程序启动时即可直接使用,无需手动创建。
| 字段 | 类型 | 含义 |
|---|---|---|
System.in |
InputStream |
标准输入流,默认来自键盘 |
System.out |
PrintStream |
标准输出流,默认输出到控制台 |
System.err |
PrintStream |
标准错误流,默认输出到控制台 |
1. 标准输入流 System.in
System.in 是一个字节输入流,直接读取得到的是字节。读取文本输入时,通常需要包装成字符流。
java
BufferedReader reader = new BufferedReader(
new InputStreamReader(System.in, StandardCharsets.UTF_8));
String input = reader.readLine();
System.out.println("你输入的是:" + input);
也可以使用 Scanner 简化解析:
java
Scanner scanner = new Scanner(System.in);
int number = scanner.nextInt();
String word = scanner.next();
2. 标准输出流 System.out
System.out 是 PrintStream 类型,提供了 print、println、printf 等便捷方法,并自动处理字符编码转换。
java
System.out.println("普通输出");
System.out.printf("格式化输出:%s 的年龄是 %d%n", "张三", 20);
3. 标准错误流 System.err
System.err 用于输出错误信息,与 System.out 分离,便于在重定向时把正常输出和错误输出分别处理。
java
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.err.println("计算出错:" + e.getMessage());
}
4. 重定向标准流
System 提供了 setIn、setOut、setErr 方法,可以把标准流重定向到文件或其他流,这在测试和日志收集场景中很实用。
java
try (PrintStream fileOut = new PrintStream("output.log")) {
System.setOut(fileOut);
System.out.println("这行内容会写入文件");
}
需要注意,重定向会影响整个 JVM 进程,使用完毕后应恢复原来的流,避免影响其他逻辑。
四、小结
流是 Java I/O 的基础抽象,它统一了不同数据源的读写方式,并通过分层与组合提供了强大的扩展能力。按数据单位可分为字节流与字符流,按方向可分为输入流与输出流,按功能层次可分为节点流与处理流。标准输入输出流则是程序与外部环境交互的最直接通道,理解它们的类型与用法,是掌握 Java I/O 的第一步。