首页 / Java 入门教程 / Java I/O 概述

Java 入门教程

Java I/O 概述

本教程共 100 篇 · 第 85 篇 · 更新于 2026-08-05 · 约 5 分钟阅读

JavaJava 入门教程IO流字节流字符流java.io

本节目标:搞懂「流」是什么、输入输出的方向怎么分、字节流和字符流各管什么,建立起 java.io 的整体地图。

程序为什么需要 I/O

到目前为止,我们写的程序数据都在内存里。程序一停,数据全没。

想让数据活得比程序久,就得把它写到硬盘文件里。想读取别人给的配置、想收发网络消息,也都要跟外部世界打交道。

这类「程序与外部之间搬运数据」的操作,统称 I/O,即 Input/Output(输入/输出)。

Java 把这套能力放在 java.io 包里,核心概念只有一个字:

流到底是什么

把流想象成一根自来水管。水管本身不存水,它只负责让水一段一段流过去。

Java 的流也一样:它不保存数据,只负责让数据一个一个地经过。数据经过的顺序是固定的,先来的先走,不能跳着取。

这根管子的两头分别接着什么,Java 并不关心。可以是硬盘上的文件、可以是网络连接、可以是内存里的一个数组、甚至可以是另一个程序。

正因为不关心,我们才能用同一套 API 处理各种数据源。这是流设计最值钱的地方。

输入还是输出,站在程序的角度看

初学者最常搞混的就是方向。记住一句话:方向永远以你自己的程序为原点

数据从外面流进程序,叫输入流(Input Stream)。比如读文件、收网络包。

数据从程序流向外面,叫输出流(Output Stream)。比如写文件、往控制台打印。

Tip

判断技巧:把「读」和「写」这两个动作代进去。你在读,就是输入;你在写,就是输出。跟文件本身没关系。

字节流与字符流的分工

Java 的流分成两大阵营,这是整个 I/O 体系最重要的一条分界线。

字节流以字节(8 位)为最小单位搬运数据。它不管这堆字节是什么含义,照单全收。图片、视频、压缩包、可执行文件,只能用字节流。

字符流以字符(Java 内部用 Unicode,一个 char 占 16 位)为最小单位。它在搬运时会自动做「字节 ↔ 字符」的编码转换。读写文本时用它,省去自己处理编码的麻烦。

一句话选型:能看懂的文本用字符流,看不懂的二进制用字节流。

Warning

用字节流去读中文文本,一个汉字在 UTF-8 下占 3 个字节。若按单字节逐个转成 char,中文必然乱码。这是初学者最高频的翻车点。

四大抽象基类

两个阵营 × 两个方向,正好四个抽象基类。整个 java.io 都是围着它们建起来的。

方向字节流字符流
输入InputStreamReader
输出OutputStreamWriter

它们都是抽象类,不能直接 new。实际用的是它们的子类,比如读文件用 FileInputStreamFileReader

四个基类各自定义了最核心的方法:InputStreamread()OutputStreamwrite()Readerread()Writerwrite()

子类再多,也逃不出这几个动作。

节点流与处理流

流还可以按「离数据源的远近」分成两类。

节点流直接连着数据源,是真正干活的那根管子。FileInputStream 直连文件,ByteArrayInputStream 直连内存数组。

处理流不直接连数据源,而是套在别的流外面,给它加功能。BufferedInputStream 加缓冲、DataInputStream 加类型读写能力。

这种「一层套一层」的写法在 Java I/O 里到处都是:

// 三层包装:文件流 → 缓冲流 → 数据流
DataInputStream in = new DataInputStream(
        new BufferedInputStream(
                new FileInputStream("data.bin")));

外层流关闭时会自动关掉内层流,所以只需要关最外面那一个。

流是同步阻塞的

java.io 里的流全部是同步阻塞的。

意思是:调用 read() 之后,当前线程会停在那里等,直到数据真的读回来才继续往下走。

int n = input.read(); // 线程卡在这行,直到读到数据或到达末尾
int m = n;            // 上一行没返回,这行不会执行

硬盘和网络比 CPU 慢好几个数量级。所以 I/O 操作耗时长、且时长无法预估,这是后面学多线程时的重要背景。

必须关闭,且用 try-with-resources 关

文件句柄、网络连接这些资源由操作系统统一管理,数量有限。用完不还,程序迟早会因为句柄耗尽而崩。

所有流都有 close() 方法。但直接调用有个陷阱:中途抛异常就跳过了 close()

Java 7 起提供了 try-with-resources 语法,把流声明在 try 后的括号里,编译器会自动生成关闭代码:

import java.io.*;

public class IoIntro {
    public static void main(String[] args) throws IOException {
        // 先写一个文件,保证下面读的时候一定存在
        try (Writer writer = new FileWriter("demo.txt")) {
            writer.write("Hello, Java I/O!");
        } // 离开代码块,writer 自动关闭

        // 再把它读回来
        try (Reader reader = new FileReader("demo.txt")) {
            int c;
            while ((c = reader.read()) != -1) {
                System.out.print((char) c);
            }
        }
        System.out.println();
    }
}

编译运行:

javac IoIntro.java
java IoIntro

输出:

Hello, Java I/O!
Note

try-with-resources 认的不是「流」,而是 AutoCloseable 接口。四大基类都实现了它,所以都能放进括号里。

历史兼容写法(Java 7 之前)长这样,现在只做对照,不要在新代码里用:

// 历史兼容:Java 7 之前的手动关闭,啰嗦且容易漏
InputStream input = null;
try {
    input = new FileInputStream("demo.txt");
    // ...读取...
} finally {
    if (input != null) {
        input.close();
    }
}

java.io 之外还有 NIO.2

java.io 是 Java 1.0 就有的老资格。Java 7 又引入了 java.nio.file 包,提供 PathFiles 两个更现代的工具。

两者不是替代关系:Files 适合简单的整文件读写和目录操作,流适合大文件的分段处理。后面第 91、92 章会专门讲。

本节小结

  • 流是一条单向的数据通道,只管顺序传输,不保存数据。
  • 方向以自己的程序为原点:读进来是输入流,写出去是输出流。
  • 二进制数据用字节流,文本数据用字符流,选错会乱码。
  • 四大基类:InputStreamOutputStreamReaderWriter
  • 处理流套在节点流外面加功能,只需关闭最外层。
  • 所有流一律用 try-with-resources 管理,不要手写 finally