标准的 Java IO API,你操作的对象是字节流(byte stream)或者字符流(character stream),而 NIO,你操作的对象是 channels 和 buffers。数据总是从一个 channel 读到一个 buffer 上,或者从一个 buffer 写到 channel 上。比如一个线程里,可以从一个 channel 读取数据到一个 buffer 上,在 channel 读取数据到 buffer 的时候,线程可以做其他的事情。当数据读取到 buffer 上后,线程可以继续处理它。
Java NIO 有三个核心组件(core components):Channels、Buffers、Selectors。
Channel(通道)
Channel 和传统 IO 中的 Stream 流很相似。只不过 Stream 是单向的,要么是 InputStream 只能进行读,要么是 OutputStream 只能进行写。而 Channel 是双向的。以下是常用的几种通道:
- FileChannel: 可以向文件读写数据
- SocketChanel: 以 TCP 来向网络连接的两端读写数据
- ServerSocketChanel: 能够监听客户端发起的 TCP 连接,并为每个 TCP 连接创建一个新的 SocketChannel
- DatagramChannel: 以 UDP 协议来向网络连接的两端读写数据
Buffer(缓冲区)
Buffer,故名思意,缓冲区,实际上是一个容器,是一个连续数组。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer。
上图描述了客户端向服务端发送数据,然后服务端接收数据的全部过程。客户端发送数据时,必须先将数据存入 Buffer 中,然后将 Buffer 中的内容写入通道。服务端这边接收数据必须通过 Channel 将数据读入到 Buffer 中,然后再从 Buffer 中取出数据来处理。
Selector(选择器)
Selector 类是 NIO 的核心类,Selector 能够检测多个注册的通道 Channel 上是否有事件发生,如果有,便获取事件然后针对每个事件进行相应的处理。这样一来,只用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写。这种方式不必为每个连接都创建一个线程,不用去维护多个线程,大大地减少了系统开销。
与 Selector 有关的一个关键类是 SelectionKey,一个 SelectionKey 表示一个到达的事件,这 2 个类构成了服务端处理业务的关键逻辑。下图表示单线程运行的 Selector 同时管理多个通道 (连接)。
Selector 的创建和可以设置监听的事件如下:
Selector selector = Selector.open(); // 创建 Selector
SelectionKey key = channel.register(selector, Selectionkey.OP_READ); // 将通道注册到 Selector 上
// register 的第二个参数指明监听哪些事件,可选的值有:
// Connect - 连接就绪
// Accept - 接收就绪
// Read - 读就绪
// Write - 写就绪
// 与之对应的检测 Channel 中什么事件或操作已经就绪的函数为:
selectionKey.isConnectable(); // 是否连接就绪
selectionKey.isAcceptable(); // 是否接收就绪
selectionKey.isReadable(); // 是否读就绪
selectionKey.isWritable(); // 是否写就绪Copy to clipboardErrorCopied
Java NIO 代码示例
下面结合示例代码,进一步理解 Java NIO 中的各项概念。
// 创建 Selector
Selector selector = Selector.open();
// 创建 ServerSocketChannel 并绑定到指定端口
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress("127.0.0.1", 1234));
// 设置 ServerSocketChannel 为 non-blocking
server.configureBlocking(false);
// 将 server channel 注册到 selector 并设置监听 OP_ACCEPT 事件
server.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// selector 被 select() 阻塞
// select() 会把注册的事件添加到 SelectionKeys (只增不减)
if (selector.select() == 0) {
continue;
}
// 获得 SelectionKey 集合, 每一个 Selectionkey 对应着一个已注册的 Channel
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
if (key.isAcceptable()) {
// 获取与 client 相连的 SocketChannel
SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();
// 同样设置为 non-blocking
channel.configureBlocking(false);
// 这里可以向 client 发送信息
channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息!").getBytes()));
// 将此 channel 的 OP_READ 事件注册到 selector
channel.register(this.selector, SelectionKey.OP_READ);
}
// 如果 channel 可读
if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
// 创建读缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
}
// 由于 select() 对 SelectionKey 集合只增不减 这里需手动移除 key
keys.remove(key);
}
}
下一节:Netty 是一个异步网络库,使 Java NIO 的功能更好用。