文章目录
- 一、IO 基础与分类
- 二、NIO 核心组件与原理
- 三、NIO 与 BIO 的实战对比
- 四、AIO 与 NIO 的区别
- 五、Netty 相关(NIO 的高级应用)
- 总结
Java 中的 IO(输入输出)和 NIO(非阻塞 IO)是面试中的重要考点,尤其在涉及高并发、高性能场景时频繁出现。以下是常见问题及核心解析:
一、IO 基础与分类
- 什么是 IO?Java 中的 IO 模型有哪些?
- IO 指数据在内存与外部设备(文件、网络等)之间的传输。
- Java 中的 IO 模型:
- BIO(Blocking IO,阻塞 IO):传统 IO,读写操作阻塞线程,直到完成。
- NIO(Non-blocking IO,非阻塞 IO):JDK 1.4 引入,基于通道(Channel)和缓冲区(Buffer),支持非阻塞操作。
- AIO(Asynchronous IO,异步 IO):JDK 1.7 引入,基于回调,操作完成后通知线程(真正的异步,区别于 NIO 的非阻塞)。
- 字节流与字符流的区别?分别有哪些核心类?
- 字节流:以字节(8 位)为单位处理数据,适用于所有类型文件(二进制、文本)。
核心类:InputStream
/OutputStream
(抽象基类)、FileInputStream
/FileOutputStream
、BufferedInputStream
/BufferedOutputStream
。- 字符流:以字符(16 位 Unicode)为单位处理数据,适用于文本文件(需处理编码)。
核心类:Reader
/Writer
(抽象基类)、FileReader
/FileWriter
、BufferedReader
/BufferedWriter
。- 区别:字节流直接操作字节,字符流通过字符编码(如 UTF-8)转换字节,需注意编码不一致导致的乱码问题。
- 缓冲流(Buffered)的作用?为什么能提高性能?
- 缓冲流通过内置缓冲区(字节数组)减少直接 IO 操作次数:例如
BufferedReader
会一次读取多个字符到缓冲区,后续read()
从缓冲区获取,而非每次访问磁盘/网络。- 性能提升:IO 操作(尤其是磁盘)比内存操作慢 10^6 倍以上,缓冲流将多次小 IO 合并为一次大 IO,降低 IO 次数。
二、NIO 核心组件与原理
- NIO 与 BIO 的本质区别是什么?
- BIO:面向流(Stream),读写是阻塞的(线程等待数据就绪),一个连接对应一个线程,高并发下资源耗尽。
- NIO:面向缓冲区(Buffer),支持非阻塞(线程可做其他事,无需等待数据),通过 selector 实现“一个线程管理多个连接”,适合高并发场景。
- NIO 的三大核心组件是什么?各自的作用?
- Buffer(缓冲区):数据容器,用于存储读写的数据(如
ByteBuffer
、CharBuffer
),底层是数组,通过position
、limit
、capacity
控制读写。- Channel(通道):双向数据通道(区别于 BIO 的单向流),可读写数据,关联缓冲区(如
FileChannel
、SocketChannel
、ServerSocketChannel
)。- Selector(选择器):多路复用器,一个线程可监控多个 Channel 的事件(如连接就绪、读就绪、写就绪),实现非阻塞 IO 管理。
- Selector 的工作原理?如何实现多路复用?
- 步骤:
- 创建
Selector
实例,将Channel
注册到Selector
(需设置为非阻塞configureBlocking(false)
),并指定关注的事件(SelectionKey.OP_READ
读、OP_WRITE
写、OP_ACCEPT
连接等)。- 调用
selector.select()
阻塞等待事件就绪(或selectNow()
非阻塞),返回就绪事件数。
3. 遍历就绪的SelectionKey
,处理对应事件(如读就绪则从Channel
读取数据到Buffer
)。- 多路复用:通过操作系统底层支持(如 Linux 的
epoll
、Windows 的IOCP
),select()
仅返回就绪的通道,避免线程无效等待,实现“一个线程管理多连接”。
- Buffer 的
flip()
、rewind()
、clear()
方法的区别?
flip()
:切换为读模式,limit = position
,position = 0
(写完数据后准备读)。rewind()
:重置读指针,position = 0
,limit
不变(重新读已写入的数据)。clear()
:清空缓冲区,position = 0
,limit = capacity
(准备重新写,数据未真正删除,后续写入会覆盖)。
三、NIO 与 BIO 的实战对比
- BIO 为什么不适合高并发场景?NIO 如何解决这个问题?
- BIO 问题:一个连接对应一个线程,若并发量为 1 万,需创建 1 万线程,线程切换和内存开销极大(每个线程栈约 1MB),导致系统崩溃。
- NIO 解决:通过 Selector 实现“单线程管理多连接”,仅在事件就绪时处理,线程数远小于连接数(如 10 个线程处理 10 万连接),降低资源消耗。
- 如何用 NIO 实现一个简单的服务器?
核心步骤:
// 1. 创建服务器通道和选择器
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false); // 非阻塞模式
Selector selector = Selector.open();// 2. 注册连接事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);while (true) {selector.select(); // 阻塞等待事件Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> iterator = keys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove(); // 避免重复处理if (key.isAcceptable()) {// 3. 处理连接事件SocketChannel clientChannel = serverChannel.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ); // 注册读事件} else if (key.isReadable()) {// 4. 处理读事件SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead > 0) {buffer.flip();// 处理数据...} else if (bytesRead == -1) {clientChannel.close(); // 连接关闭}}}
}
- NIO 中的“零拷贝”是什么?如何实现?
- 零拷贝:避免数据在用户态和内核态之间重复拷贝,提升 IO 效率(如大文件传输)。
- 实现:
FileChannel
的transferTo()
方法,通过操作系统的sendfile
系统调用,直接将文件数据从内核态的磁盘缓冲区传输到网络缓冲区,无需用户态参与。- 应用:NIO 实现的文件服务器、视频流传输等场景。
四、AIO 与 NIO 的区别
- AIO 与 NIO 的核心区别?各自的适用场景?
- NIO:非阻塞同步 IO,线程需主动调用
select()
轮询事件是否就绪,仍需线程参与。- AIO:异步 IO,线程发起读写操作后即可返回,操作完成后由操作系统通知线程(回调函数),无需主动轮询。
- 适用场景:
- NIO:高并发短连接(如 HTTP 服务器),需处理大量连接但每个连接数据量小。
- AIO:长连接且 IO 操作耗时(如文件下载、数据库查询),异步等待不阻塞线程。
- 为什么 AIO 在 Java 中应用不如 NIO 广泛?
- 底层支持差异:Windows 对 AIO 支持较好(
IOCP
),但 Linux 直到内核 2.6 才支持,且 Java 对 Linux AIO 的封装不完善。- 编程复杂度:AIO 基于回调,逻辑分散,调试困难;NIO 模型更成熟,框架(如 Netty)已基于 NIO 优化,性能接近 AIO。
五、Netty 相关(NIO 的高级应用)
- Netty 是什么?为什么比原生 NIO 好用?
- Netty 是基于 NIO 的高性能网络框架,简化了 NIO 的复杂编程(如解决原生 NIO 的
Selector
空轮询 bug、提供编解码器等)。- 优势:封装 NIO 细节、提供断线重连/心跳检测等功能、支持多种协议(HTTP、WebSocket)、线程模型优化(主从 Reactor 模型)。
- Netty 的线程模型?如何实现高并发?
- 主从 Reactor 模型:
- 主 Reactor(Boss 线程):处理连接请求,将建立的连接分配给从 Reactor。
- 从 Reactor(Worker 线程):处理 IO 事件(读写),调用业务逻辑。
- 高并发原因:基于 NIO 多路复用,减少线程数;通过事件驱动和异步处理,避免阻塞;内存池减少对象创建开销。
总结
IO/NIO 面试重点考察 BIO 与 NIO 的本质区别、NIO 三大组件的协作原理、非阻塞与多路复用的实现 及 高并发场景下的 IO 模型选择。回答时需结合底层机制(如系统调用、内核态/用户态)和实战场景(如 Netty 的应用),体现对高性能 IO 的理解。