IO 核心概念
你有没有想过:当你在 Java 里写 System.out.println("hello") 时,这条字符串是怎么跑到屏幕上去的?
这条语句背后,经历了从 Java 堆内存 → JVM 内部缓冲区 → 操作系统内核缓冲区 → 屏幕驱动 → 显示器,一连串的数据搬运。每一次搬运,都是一次 IO。
理解 Java 的 IO,本质上就是理解这个数据搬运的全过程。
什么是 IO
IO(Input/Output),输入与输出。在 Java 中,一切与外部世界的数据交换都叫 IO:
- 读文件:磁盘 → 程序内存
- 写文件:程序内存 → 磁盘
- 网络收包:网卡 → 程序内存
- 网络发包:程序内存 → 网卡
- 读键盘输入:终端 → 程序内存
- 写屏幕输出:程序内存 → 终端
核心矛盾:内存速度极快(纳秒级),而外部设备速度极慢(磁盘毫秒级,网络微秒级)。IO 的所有复杂性都来自这个速度差。
两条主线:字节与字符
Java 的 IO 围绕两条线展开:
字节流
以 byte 为单位处理数据,InputStream / OutputStream 是两个根类。
InputStream in = new FileInputStream("data.dat");
int b = in.read(); // 读 1 个字节,返回 0~255 或 -1(结束)不管数据是什么——图片、视频、PDF、压缩包——磁盘上存的就是一串字节。所以字节流是所有 IO 的地基。
字符流
以 char(16 位 Unicode)为单位处理数据,Reader / Writer 是两个根类。
Reader reader = new FileReader("text.txt");
int c = reader.read(); // 读 1 个字符,返回 Unicode 码点字符流解决的是文本文件的读写问题——它底层用的还是字节流,只是加了一层字符编码转换。UTF-8 的「中」字是 3 个字节,但字符流一次读出来的是一个完整的字符。
输入与输出
每个流都有方向:
输入流(InputStream / Reader) ← 数据从外部进入程序
输出流(OutputStream / Writer) ← 数据从程序输出到外部常见混淆:FileInputStream 是输入(读文件),FileOutputStream 是输出(写文件)。
BIO 与 NIO
Java 有两套 IO 系统:
BIO(Blocking IO)
传统的流式 IO。read() 会一直阻塞,直到数据到来或流关闭。
// BIO 的特点是:一个线程只能处理一个连接
Socket socket = server.accept(); // 阻塞
InputStream in = socket.getInputStream();
int b = in.read(); // 这里也会阻塞每来一个连接就创建一个线程,线程数量受限于系统资源。
NIO(New IO / Non-blocking IO)
JDK 1.4 引入。通过 Buffer(缓冲区)、Channel(通道)、Selector(选择器)三个核心组件,实现单线程管理多个连接。
// NIO 的特点:非阻塞 + 多路复用
selector.select(); // 阻塞,但可以同时监听多个 Channel一个线程轮询所有连接的状态,只在连接真正可读/可写时才处理,大幅降低线程开销。
同步 vs 异步
| 同步(Synchronous) | 异步(Asynchronous) | |
|---|---|---|
| 调用方式 | 调用后等待结果返回 | 调用后立即返回,结果通过回调通知 |
| 线程状态 | 等待期间阻塞 | 不阻塞,可继续做其他事 |
| 实现 | BIO 和 NIO 的默认模式 | JDK 7+ AIO(AsynchronousChannel) |
本节要点
- IO 的本质是数据在不同存储介质之间的搬运
- 字节流(InputStream/OutputStream)是地基,字符流(Reader/Writer)是上层建筑
- 字符流底层是字节流,加了一层编码转换
- BIO 是同步阻塞,NIO 是同步非阻塞多路复用,AIO 是异步非阻塞
