《Java 100 天进阶之路》第36篇:选择最适合自己的NIO,一探流技术
第36篇:选择最适合自己的NIO,一探流技术
📌 系列导航:《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第35篇:Java异常处理最佳实践 |
➡️ 下一篇:第37篇:Java内部类及其作用
一、核心知识点
- BIO(Blocking I/O)、NIO(Non-blocking I/O)、AIO(Asynchronous I/O)的区别
- 传统流体系:字节流、字符流、缓冲流、转换流
- NIO 核心组件:Channel(通道)、Buffer(缓冲区)、Selector(多路复用器)
- 文件操作与网络操作示例
- 压缩流(GZIP)等
二、通俗讲解(1分钟开心学)
1. I/O 模型的演进
- BIO(同步阻塞):一个连接一个线程,线程在读写数据时会阻塞。适合连接数少、处理快的场景。
- NIO(同步非阻塞):一个线程可以管理多个连接(Selector),当数据准备好时才处理。适合连接数多但每个连接不频繁通信的场景(如聊天服务器)。
- AIO(异步非阻塞):数据准备好后回调通知,不需要主动轮询。适合高并发、长连接的场景(Java 7 引入,但实际使用不如 NIO 普遍)。
2. 传统流(BIO)体系
- 字节流:
InputStream/OutputStream,处理二进制数据(图片、音视频)。 - 字符流:
Reader/Writer,处理文本,自动处理字符编码。 - 缓冲流:
BufferedInputStream/BufferedReader,减少系统调用,提高性能。 - 转换流:
InputStreamReader/OutputStreamWriter,字节与字符之间的桥梁。
3. NIO 三大核心
- Channel:双向通道,可读可写(类似流但功能更强)。
- Buffer:缓冲区,数据容器(如
ByteBuffer)。 - Selector:多路复用器,监听多个 Channel 的事件(读就绪、写就绪等)。
4. 为什么 NIO 性能更好?
BIO 每个连接占用一个线程,线程上下文切换开销大。NIO 通过 Selector 复用线程,只有数据准备好时才处理,资源占用低。
生活类比:
BIO 就像餐厅里每个顾客配一个服务员,服务员一直守着,顾客不说话也站着。
NIO 就像一个服务员同时服务多个顾客,哪个顾客举手(数据就绪)就去哪个,其他时间可以干别的。
三、实操代码案例 + 场景说明
场景:文件复制,分别用 BIO 流和 NIO 的 FileChannel 实现,并对比效率。
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.*;
public class NIODemo {
// BIO 文件复制
public static void copyWithBIO(String src, String dest) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
}
// NIO 文件复制(使用 FileChannel)
public static void copyWithNIO(String src, String dest) throws IOException {
try (FileChannel srcChannel = FileChannel.open(Paths.get(src), StandardOpenOption.READ);
FileChannel destChannel = FileChannel.open(Paths.get(dest), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
srcChannel.transferTo(0, srcChannel.size(), destChannel);
}
}
// NIO 带缓冲区的手动复制
public static void copyWithNIOBuffer(String src, String dest) throws IOException {
try (FileChannel srcChannel = new FileInputStream(src).getChannel();
FileChannel destChannel = new FileOutputStream(dest).getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(8192);
while (srcChannel.read(buffer) != -1) {
buffer.flip();
destChannel.write(buffer);
buffer.clear();
}
}
}
// GZIP 压缩(BIO 流的典型应用)
public static void compressGZIP(String src, String dest) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new GZIPOutputStream(new FileOutputStream(dest))) {
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
}
}
public static void main(String[] args) throws Exception {
String src = "largefile.dat";
String dest = "copy.dat";
long start = System.nanoTime();
copyWithBIO(src, dest);
System.out.println("BIO time: " + (System.nanoTime() - start) / 1e6 + " ms");
start = System.nanoTime();
copyWithNIO(src, dest);
System.out.println("NIO transferTo: " + (System.nanoTime() - start) / 1e6 + " ms");
}
}
NIO 服务器简例(理解 Selector):
// 核心代码示意
Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select(); // 阻塞等待事件
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
if (key.isAcceptable()) { /* 接受连接 */ }
else if (key.isReadable()) { /* 读数据 */ }
}
keys.clear();
}
四、避坑要点
| 错误/误区 | 后果 | 正确做法 |
|---|---|---|
NIO 中忘记 flip() |
读不到数据或读到错误数据 | 写后读前调用 flip() |
| 直接缓冲区频繁分配 | 性能下降 | 复用缓冲区,使用对象池 |
| Selector 处理完事件不移除 | 无限循环 | 用完后 iterator.remove() |
| 混淆字符流和字节流 | 乱码或数据损坏 | 文本用字符流,二进制用字节流 |
五、面试高频考点
Q1:BIO、NIO、AIO 的区别?
BIO 阻塞,一连接一线程;NIO 同步非阻塞,通过 Selector 管理多个连接;AIO 异步非阻塞,基于回调。NIO 适合高并发低流量(如聊天),AIO 适合高并发长连接(但实现复杂)。
Q2:NIO 的核心组件有哪些?
Channel(双向通道)、Buffer(数据容器)、Selector(事件多路复用器)。
Q3:什么是零拷贝?
FileChannel.transferTo()可以直接将数据从文件系统缓存传输到网络套接字缓冲区,绕过用户态内存拷贝,提高大文件传输效率。
六、练习题
- 对比:分别用 BIO 和 NIO 实现文件复制,比较耗时。
- 编程:使用 NIO 的
FileChannel实现一个方法,将文件内容全部读取到ByteBuffer中并打印。 - 简答:为什么 NIO 适合高并发场景?
📊 你的学习进度
- 当前:第36篇 / 共44篇 · 第六阶段:NIO、泛型、JVM内幕、字节码(第36~44篇)
- ✅ 已完成:第1~35篇
- 📖 正在学:第36篇
- ⏳ 待学习:第37~44篇
👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇
💡 本专栏每篇都包含:避坑表 + 面试高频考点 + 练习题。每天30分钟,100天拿offer!
👉 下一篇文章预告
《第37篇:Java内部类及其作用》
内容简介:成员内部类、静态内部类、局部内部类、匿名内部类,访问规则,内存泄漏风险。
💡 学完这篇,你将掌握内部类的所有用法,写出更优雅的代码结构。
📌 《Java 100 天进阶之路 | 从入门到上岗就业》 每天一篇,建议收藏 + 关注,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!
📌 除了Java,我也在深挖智能物流实战(出版社WMS、托盘调度、机器学习落地)。如果你对技术在不同领域的实战感兴趣,欢迎点击我的头像,看看专栏《出版社物流WMS智能调度实战》。技术相通,思路可鉴。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)