Java NIO非阻塞I/O与传统阻塞式I/O的区别

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<SelectionKey> selectedKeys = selector.selectedKeys();

for (SelectionKey key : selectedKeys) {

if (key.isReadable()) {

// 处理可读事件

}

if (key.isWritable()) {

// 处理可写事件

}

// 其他事件处理...

}

  1. 取消通道的注册:当你不再需要监听某个通道时,可以调用通道的 cancel() 方法取消通道的注册。例如:key.cancel();

  2. 关闭 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类的实例:

java 复制代码
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<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> 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()方法将位置重置为标记的位置。
    缓冲区具有以下几个基本操作:

  5. 写入数据:使用put()方法向缓冲区中写入数据,可以一次性写入多个元素或逐个写入。

  6. 读取数据:使用get()方法从缓冲区中读取数据,可以一次性读取多个元素或逐个读取。

  7. 翻转(Flip):翻转缓冲区,将限制设置为当前位置,将位置设置为0,用于从缓冲区读取数据。

  8. 清空(Clear):清空缓冲区,将位置设置为0,将限制设置为容量,用于向缓冲区写入数据。

  9. 压缩(Compact):压缩缓冲区,将未读数据复制到缓冲区的起始位置,并将位置设置为未读数据的末尾,用于继续写入数据。

缓冲区在进行数据读写时,会根据当前位置和限制来确定可读写的数据范围,避免了对整个缓冲区的操作,提高了效率。

总结来说,Java NIO的缓冲区是用于存储数据的中间载体,提供了一组API来方便地读取和写入数据。通过合理使用缓冲区,可以提高数据读写的效率和性能。

java 复制代码
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 的基本使用,在实际应用中,您可能需要处理更多的细节,例如异常处理、线程管理等。
java 复制代码
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();
    }
}

利用Java NIO实现简单的服务器与客户端通讯-CSDN博客

Java的NIO提供了非阻塞I/O机制的包-CSDN博客

相关推荐
Theodore_10229 分钟前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸1 小时前
01-spring security认证笔记
java·笔记·spring
世间万物皆对象2 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
没书读了2 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
----云烟----2 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024062 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
小二·2 小时前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic3 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it3 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
懒洋洋大魔王3 小时前
RocketMQ的使⽤
java·rocketmq·java-rocketmq