Java NIO(New Input/Output)是Java中用于处理非阻塞I/O操作的一组API。它引入了一种新的I/O模型,提供了更高效、更可伸缩的方式来处理I/O操作。
Java NIO的核心组件是通道(Channel)和缓冲区(Buffer)。通道是对原始I/O操作的一种抽象,可以通过它读取和写入数据。缓冲区是一块内存区域,用于存储数据,它使得数据的读写更加高效。
相对于传统的Java I/O(也称为流式I/O),Java NIO具有以下优点:
1. 非阻塞式:Java NIO提供非阻塞I/O操作,可以在等待I/O完成时同时执行其他任务,提高系统的效率。
2. 选择器(Selector):选择器是Java NIO的一个重要组件,可以监视多个通道的I/O事件,从而实现单线程处理多个通道的能力。
3. 内存管理:Java NIO使用直接缓冲区,将数据直接存储到物理内存中,避免了传统I/O中数据在Java堆上的复制过程,提高了效率。
4. 网络编程支持:Java NIO提供了更多的网络编程支持,例如Socket通信和服务器端的Channel和Selector。
总结来说,Java NIO提供了一种更高效、更可伸缩的方式来处理I/O操作,尤其适用于开发高性能的网络应用程序。
--------------
1. NIO和传统I/O的区别:
- NIO是非阻塞I/O,而传统I/O是阻塞式的。在传统I/O中,当一个线程在进行I/O操作时会被阻塞,而NIO可以在进行I/O操作时继续执行其他任务。
- NIO使用了通道和缓冲区的概念,而传统I/O使用了流的概念。
- NIO提供了选择器(Selector)的机制,可以同时监控多个通道的事件,而传统I/O需要使用多个线程来处理多个连接。
2. 通道和缓冲区的基本操作:
- 通道(Channel)是数据的载体,负责读取和写入数据。可以通过FileChannel、SocketChannel、ServerSocketChannel等类获取通道。
- 缓冲区(Buffer)是用来存储数据的区域。可以通过ByteBuffer、CharBuffer、IntBuffer等类创建不同类型的缓冲区。
- 通道的读取和写入都是通过缓冲区来进行的。可以使用read方法从通道将数据读取到缓冲区,使用write方法将数据从缓冲区写入到通道。
3. 选择器和选择键:
- 选择器(Selector)是用来监听通道事件的对象。可以使用Selector.open()来创建一个选择器。
- 选择键(SelectionKey)是与通道一起使用的对象,它表示了注册在选择器上的通道以及对应的事件类型。
- 选择器通过register方法将通道注册到选择器上,并指定监听的事件类型。
- 选择器使用select方法监控通道事件,并返回有事件发生的通道数。
- 可以使用selectedKeys方法获取已经发生事件的选择键,然后依次处理相应的事件。
4. 非阻塞式通信的原理和优势:
- 非阻塞式通信在进行I/O操作时不会阻塞当前线程,而是立即返回,无论数据是否就绪。
- 非阻塞式通信通过不断轮询来判断数据是否就绪,从而实现对多个通道的同时管理和处理。
- 与传统I/O相比,非阻塞式通信可以更高效地处理多个并发连接,并减少线程的开销。
5. 非阻塞式通信的实现方式:通过Selector监听通道事件并处理非阻塞读写操作。
- 首先创建一个选择器,并将通道注册到选择器上。
- 使用一个线程不断调用选择器的select方法来监听通道事件。
- 当有通道事件发生时,通过获取选择键,可以判断具体是哪个通道有事件发生,然后进行相应的读写操作。
----------------
Selector 是 Java NIO 中的关键组件,它提供了一种监视多个通道的机制,以检测哪些通道已经准备好进行 I/O 操作(如读、写、连接等)。通过 Selector,你可以使用单个线程来管理多个通道,从而实现高效的 I/O 复用。
以下是关于 Selector 类的一些重要说明:
1. 创建 Selector 对象:可以通过调用 Selector.open() 方法创建一个 Selector 实例。例如:Selector selector = Selector.open();
2. 注册通道:通道(Channel)需要注册到 Selector 上才能被 Selector 进行监视。通常情况下,你需要调用通道的 register() 方法将通道注册到 Selector 上,并指定所感兴趣的事件(如读、写、连接等)。例如:channel.register(selector, SelectionKey.OP_READ);
3. 选择操作:一旦通道注册到 Selector 上,你可以调用 Selector 的 select() 方法阻塞地等待通道就绪事件的发生。select() 方法会返回就绪通道的数量。例如:int readyChannels = selector.select();
4. 获取就绪的通道:通过调用 selector.selectedKeys() 方法可以获取到一组已经就绪的 SelectionKey 集合。你可以遍历这个集合,逐个处理就绪的通道。例如:
Set
for (SelectionKey key : selectedKeys) {
if (key.isReadable()) {
// 处理可读事件
}
if (key.isWritable()) {
// 处理可写事件
}
// 其他事件处理...
}
5. 取消通道的注册:当你不再需要监听某个通道时,可以调用通道的 cancel() 方法取消通道的注册。例如:key.cancel();
6. 关闭 Selector:当你不再需要 Selector 时,应该调用其 close() 方法关闭 Selector,释放相关资源。例如:selector.close();
以上是关于 Selector 类的简要说明。使用 Selector 可以极大地提高网络编程的效率和性能,特别适用于需要管理多个并发连接的场景。
---------------
Selector和SelectionKey是Java NIO中与选择器和选择键相关的两个重要类。下面是它们常用的方法:
Selector类的方法:
- open():创建一个新的选择器。
- isOpen():检查选择器是否处于打开状态。
- close():关闭选择器。
- select():阻塞直到至少有一个通道已经准备就绪,返回就绪通道的数量。
- select(timeout):最多阻塞timeout毫秒,或者直到至少有一个通道已准备就绪。
- selectNow():立即返回,不阻塞,如果有通道就绪则返回就绪通道的数量。
- selectedKeys():返回已选择键集合的集合视图。
- keys():返回现在已经注册到选择器上的键集合。
- wakeup():唤醒正在select操作中阻塞的线程。
- selectableChannels():返回当前已注册到选择器的通道的集合。
SelectionKey类的方法:
- channel():返回与此键关联的通道。
- selector():返回此键的选择器。
- isValid():检查此键是否有效。
- interestOps():获取或设置此键的感兴趣的操作集。
- interestOps(int ops):设置此键的感兴趣的操作集。
- isReadable():检查键的通道是否已准备好读取操作。
- isWritable():检查键的通道是否已准备好写入操作。
- isConnectable():检查键的通道是否已准备好连接操作。
- isAcceptable():检查键的通道是否已准备好接受操作。
- attach(Object obj):关联一个对象到此键。
- attachment():获取此键的附加对象。
- cancel():取消此键的通道在其选择器中的注册。
以上是Selector和SelectionKey类的一些常用方法。通过使用它们,你可以实现对通道的事件监听和处理,以及管理多个通道的并发操作。详情可参考Java官方文档中关于Selector和SelectionKey的说明。
---------------------
以下是Java官方文档中关于Selector和SelectionKey的说明(摘自Java 11文档):
Selector类的说明:
- Selector是可选择通道的多路复用器。它可以通过单个线程,使用选择操作来监视多个通道的事件。
- 可以使用Selector.open()方法打开一个选择器,使用register()方法将通道注册到选择器上。
- 一旦通道注册到选择器上,通过选择器的select()方法可以阻塞地等待通道就绪事件的发生。
- 选择器是线程安全的,可以在多线程环境下共享使用。
SelectionKey类的说明:
- SelectionKey表示注册在选择器上的通道,并维护了通道与选择器之间的关联关系。
- 通过SelectionKey可以检索与通道关联的选择器,以及感兴趣的操作集合(interest set)和就绪的操作集合(ready set)。
- 可以使用isReadable()、isWritable()、isConnectable()和isAcceptable()等方法来检查通道就绪的特定操作。
- 通过修改interest set,可以动态改变对通道感兴趣的操作,通过修改ready set,可以处理通道已准备就绪的操作。
- 可以通过attach()和attachment()方法关联和检索自定义的附加对象到SelectionKey上。
- SelectionKey是不可重用的,一旦取消注册后将会失效。
请注意,以上是对官方文档中关于Selector和SelectionKey的简要说明。如需详细了解它们的更多细节和使用方法,请参考Java官方文档中关于java.nio.channels包的相关章节。
----------
以下是使用Selector和SelectionKey类的实例:
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.channels.Selector;
- import java.nio.channels.ServerSocketChannel;
- import java.nio.channels.SelectionKey;
- import java.util.Iterator;
- import java.util.Set;
-
- public class SelectorExample {
- public static void main(String[] args) throws IOException {
- // 创建选择器
- Selector selector = Selector.open();
-
- // 创建ServerSocketChannel并绑定端口
- ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
- serverSocketChannel.socket().bind(new InetSocketAddress(8888));
- serverSocketChannel.configureBlocking(false);
-
- // 将通道注册到选择器上,并指定感兴趣的事件为接收连接
- serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
-
- while (true) {
- // 阻塞等待就绪的通道,最多等待10秒
- int readyChannels = selector.select(10000);
-
- if (readyChannels == 0) {
- continue;
- }
-
- // 获取已经就绪的通道集合
- Set
selectedKeys = selector.selectedKeys(); - Iterator
keyIterator = selectedKeys.iterator(); -
- while (keyIterator.hasNext()) {
- SelectionKey key = keyIterator.next();
-
- if (key.isAcceptable()) {
- // 处理连接事件
- // ...
- }
-
- if (key.isReadable()) {
- // 处理读事件
- // ...
- }
-
- if (key.isWritable()) {
- // 处理写事件
- // ...
- }
-
- // 处理完事件后,需要从已选择键集合中移除
- keyIterator.remove();
- }
- }
- }
- }
上述示例演示了如何使用Selector和SelectionKey类来监听和处理通道事件。在主循环中,使用select()方法阻塞等待就绪的通道,然后使用迭代器遍历就绪的选择键集合,判断具体是哪种事件发生,并进行相应的处理。最后,需要使用迭代器的remove()方法将已处理的选择键从集合中移除。
请注意,在实际应用中,你可能还需要处理异常、关闭通道、写入数据等其他操作。以上示例仅提供了基本的框架。根据你的实际需求和业务逻辑,你可以根据选择键的就绪状态进行相应的操作。
解释一下这两行代码的具体作用:
1. selector.selectedKeys():这个方法返回一个Set,包含了当前已经就绪的选择键(SelectionKey)的集合。就绪的选择键指的是那些可以执行操作(如读、写、连接、接受等)的通道所对应的选择键。这个集合是动态的,会随通道的就绪状态的变化而变化。
2. selectedKeys.iterator():通过调用集合的iterator()方法,可以获取到一个迭代器(Iterator),用来遍历集合中的元素。在这里,我们将选择键集合的迭代器赋值给了keyIterator变量,后续可以使用这个迭代器来依次访问集合中的选择键元
------------
Java NIO的缓冲区(Buffer)是用于存储数据的一块连续内存区域,它是NIO中数据读写的中间载体。缓冲区提供了一组API来方便地读取和写入数据,并且可以跟踪读写位置、限制和容量等信息。
Java NIO中的缓冲区是抽象类java.nio.Buffer的子类,常用的缓冲区实现类有ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer和DoubleBuffer,分别用于存储不同类型的数据。
以下是缓冲区的一些重要概念:
1. 容量(Capacity):缓冲区的容量是它所能存储的数据的大小,一旦定义后不可变。
2. 限制(Limit):限制是缓冲区中有效数据的末尾位置,初始时与容量相等,随着读写操作而改变。
3. 位置(Position):位置表示下一个要读取或写入的元素的索引,初始时为0,随着读写操作而改变。
4. 标记(Mark):标记是一个备忘位置,通过调用mark()方法设置,之后可以通过调用reset()方法将位置重置为标记的位置。
缓冲区具有以下几个基本操作:
1. 写入数据:使用put()方法向缓冲区中写入数据,可以一次性写入多个元素或逐个写入。
2. 读取数据:使用get()方法从缓冲区中读取数据,可以一次性读取多个元素或逐个读取。
3. 翻转(Flip):翻转缓冲区,将限制设置为当前位置,将位置设置为0,用于从缓冲区读取数据。
4. 清空(Clear):清空缓冲区,将位置设置为0,将限制设置为容量,用于向缓冲区写入数据。
5. 压缩(Compact):压缩缓冲区,将未读数据复制到缓冲区的起始位置,并将位置设置为未读数据的末尾,用于继续写入数据。
缓冲区在进行数据读写时,会根据当前位置和限制来确定可读写的数据范围,避免了对整个缓冲区的操作,提高了效率。
总结来说,Java NIO的缓冲区是用于存储数据的中间载体,提供了一组API来方便地读取和写入数据。通过合理使用缓冲区,可以提高数据读写的效率和性能。
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.nio.channels.ServerSocketChannel;
- import java.nio.channels.SocketChannel;
-
- public class NioServer {
- public static void main(String[] args) throws IOException {
- ServerSocketChannel serverChannel = ServerSocketChannel.open();
- serverChannel.socket().bind(new InetSocketAddress(9999));
- serverChannel.configureBlocking(false);
-
- Selector selector = Selector.open();
- serverChannel.register(selector, SelectionKey.OP_ACCEPT);
-
- while (true) {
- if (selector.select() > 0) {
- for (SelectionKey key : selector.selectedKeys()) {
- if (key.isAcceptable()) {
- SocketChannel clientChannel = serverChannel.accept();
- clientChannel.configureBlocking(false);
- clientChannel.register(selector, SelectionKey.OP_READ);
- } else if (key.isReadable()) {
- SocketChannel clientChannel = (SocketChannel) key.channel();
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- int bytesRead = clientChannel.read(buffer);
- if (bytesRead > 0) {
- System.out.println("Received from client: " + new String(buffer.array(), 0, bytesRead));
- buffer.flip();
- clientChannel.write(buffer);
- }
- }
- }
- selector.selectedKeys().clear();
- }
- }
- }
- }
- // 在这个例子中,服务器端使用 ServerSocketChannel 监听端口,当有客户端连接时,接受连接并使用 SocketChannel 与客户端通信。
- // Selector 用于监听多个客户端连接,并使用 SelectionKey 来管理每个连接的状态。
-
- // 客户端使用 SocketChannel 连接到服务器,然后使用 ByteBuffer 发送和接收数据。
-
- // 请注意,这个例子仅用于演示 Java NIO 的基本使用,在实际应用中,您可能需要处理更多的细节,例如异常处理、线程管理等。
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.channels.SocketChannel;
-
- public class NioClient {
- public static void main(String[] args) throws IOException {
- SocketChannel clientChannel = SocketChannel.open();
- clientChannel.connect(new InetSocketAddress("localhost", 9999));
-
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- buffer.put("Hello, server!".getBytes());
- buffer.flip();
- clientChannel.write(buffer);
-
- buffer.clear();
- int bytesRead = clientChannel.read(buffer);
- if (bytesRead > 0) {
- System.out.println("Received from server: " + new String(buffer.array(), 0, bytesRead));
- }
-
- clientChannel.close();
- }
- }