在Java 中,IO(input/output)和 NIO(new Input/output) 是两种不同的输入输出处理机制。它们各自有不同的设计理念和使用场景,理解这两者的区别对于优化性能、提高应用的响应速度非常重要。
一、IO(传统输入输出)
IO是java最早的输入输出方式,通过流(Stream)来读写数据。主要包括字节流和字符流。通过阻塞式的操作来实现数据的读写。
1、特点
阻塞式(Blocking):每次调用读写操作时,线程会被阻塞,直到数据完全读取或写入。也就是说,io操作会让线程一直等待,知道数据准备好。
同步:每个线程只能处理一个连接,不能同时处理多个客户端请求。需要多个线程来同时处理多个IO操作。
易于使用:传统IO库简单直观适合小型应用或数据量较小的场景。
数据流模型:基于流模型,数据按顺序从源流传输到目标流。
2、主要类
- inputStream 和 outputStream字节流
- reader 和 writer:字符流
3、示例:
java
// 读取文件内容
FileInputStream fis = new FileInputStream("file.txt");
int data;
while ((data = fis.read()) != -1) {
System.out.print((char) data);
}
fis.close();
二、NIO(new input/output)
NIO 是 Java 在 JDK 1.4 中引入的新的 IO 库,相较于传统 IO,它提供了非阻塞式的 I/O 操作。NIO 使用 通道(Channel) 和 缓冲区(Buffer) 来进行数据的读写。
1、特点:
非阻塞式(Non-blocking):NIO 通过Selector(选择器)和Channel(通道)来支持非阻塞I/O操作。允许线程在等待数据时做其他事情,一个线程可以处理多个通道上的I/O操作。
异步:非阻塞I/O操作使得程序能够在等待I/O操作完成的同时,继续执行其他任务,从而提高了系统性能。
支持大文件和高并发:NIO更适合处理大量数据和高并发请求。
NIO 是 Java 在 JDK 1.4 中引入的新的 IO 库,相较于传统 IO,它提供了非阻塞式的 I/O 操作。NIO 使用 通道(Channel) 和 缓冲区(Buffer) 来进行数据的读写。
基于事件驱动:NIO 通过 Selector 机制(监听多个通道上的事件),可以让一个线程管理多个 I/O 操作。
2、主要类:
-
Channel(通道):可以用来读写数据,常见的通道类有 FileChannel、SocketChannel、DatagramChannel 等。
-
Buffer(缓冲区):数据的读写都发生在缓冲区中,通过 ByteBuffer、CharBuffer 等类来管理数据。
-
Selector(选择器):通过选择器,一个线程可以管理多个通道的 I/O 操作。
3、 示例:
java
// 使用 NIO 读取文件内容
Path path = Paths.get("file.txt");
Charset charset = Charset.forName("UTF-8");
try (BufferedReader reader = Files.newBufferedReader(path, charset)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
三、IO 和 NIO 的区别
| 特点 | IO | NIO |
|---|---|---|
| 阻塞/非阻塞 | 阻塞式,读写时会阻塞当前线程 | 非阻塞式,线程可以在等待 I/O 时继续做其他工作 |
| 模型 | 基于流(Stream)的模型 | 基于通道(Channel)和缓冲区(Buffer)模型 |
| 适用场景 | 适用于少量数据或简单应用 | 适用于大规模数据、高并发、长时间连接等场景 |
| 多线程支持 | 每个线程处理一个 I/O 操作,需要多个线程处理多个请求 | 一个线程可以处理多个通道上的 I/O 操作 |
| 数据读取方式 | 按顺序读取,通过流逐字节或逐行处理数据 | 数据通过缓冲区(Buffer)批量读取和写入 |
| 使用难度 | 简单易用,学习曲线较低 | 相对复杂,学习曲线较高,需要理解缓冲区、选择器等概念 |
| 性能 | 在高并发场景下性能较低 | 高性能,尤其适用于高并发、大文件处理等场景 |
四、NIO 中通道和选择器的概念
一、通道(Channel)
通道 是 NIO 中用于数据传输的主要组件。它类似于传统 I/O 中的流,但在设计上更为灵活和高效。通道是双向的,即可以同时进行读取和写入操作。
1️⃣ 通道的概念
通道(Channel)代表了连接 I/O 操作的媒介。你可以通过通道来执行读取和写入操作,通道本身并不直接存储数据,而是通过缓冲区(Buffer)来与数据交互。
2️⃣ 通道的主要类型
NIO 中的通道有多种类型,常见的有:
-
FileChannel:用于文件 I/O 操作,提供文件的读取和写入功能。
- 示例:用于读取文件中的字节或将数据写入文件。
-
SocketChannel:用于网络 I/O 操作,通过 TCP 协议与远程计算机进行通信。
- 示例:用于客户端与服务器之间的数据传输。
-
DatagramChannel:用于通过 UDP 协议进行网络 I/O 操作。
- 示例:用于实时性要求较高的通信,像 DNS 查询、VoIP(语音通信)等。
-
ServerSocketChannel:用于服务器端监听并接受客户端连接请求。
- 示例:用来实现一个基于 NIO 的服务器。
3️⃣ 通道的基本操作
通道的基本操作包括 读 和 写,但是通道本身并不直接处理数据,它通过 缓冲区(Buffer) 来执行数据的存储和传输。
-
读取数据:read() 方法会把数据从通道中读取到缓冲区。
-
写入数据:write() 方法会把缓冲区中的数据写入到通道中。
java
// 示例:使用 FileChannel 读取文件
FileChannel channel = new FileInputStream("file.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer); // 读取文件内容到缓冲区
4️⃣ 通道与流的区别
-
流(Stream):一次只能操作一个方向的数据传输(只能读取或写入)。
-
通道(Channel):可以双向操作数据(既能读也能写)。
二、选择器(Selector)
选择器 是 NIO 中用于实现多路复用(Multiplexing)的核心组件。它允许一个线程同时管理多个通道(Channel),并能够处理多个 I/O 操作,而不需要为每个通道创建独立的线程。
1️⃣ 选择器的概念
选择器(Selector)是一个多路复用器,用于检查多个通道的状态。通过选择器,线程可以监控多个通道上的事件(如是否可读、可写、连接已完成等),当事件发生时,线程才会处理这些通道上的操作。
2️⃣ 选择器的工作流程
注册通道:将通道注册到选择器,并指定感兴趣的事件(如 OP_READ、OP_WRITE、OP_CONNECT 等)。
轮询事件:选择器不断轮询各个注册的通道,检查它们的状态,若某个通道准备好进行 I/O 操作(如数据可以读取或写入),就会将该通道加入到已选择的通道列表中。
处理 I/O 操作:当事件发生时,线程可以对该通道执行相应的 I/O 操作。
3️⃣ 选择器的核心方法
open():创建一个新的选择器。
select():阻塞并等待通道事件的发生,返回就绪的通道数。
selectedKeys():返回一个包含已就绪通道的集合,可以通过它来遍历就绪的通道并进行操作。
java
// 示例:使用 Selector 监听通道事件
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); // 设置为非阻塞模式
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select(); // 阻塞直到有事件发生
Set<SelectionKey> selectedKeys = selector.selectedKeys();
for (SelectionKey key : selectedKeys) {
if (key.isAcceptable()) {
// 处理连接请求
} else if (key.isReadable()) {
// 处理读取事件
}
selectedKeys.remove(key);
}
}
4️⃣ 选择器的事件类型
OP_READ:通道可读,数据可以从通道读取。
OP_WRITE:通道可写,数据可以写入通道。
OP_CONNECT:连接已经建立。
OP_ACCEPT:服务器端通道准备好接受新的客户端连接。