• 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 selectedKeys = selector.selectedKeys();
    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类的实例:

    1. import java.io.IOException;
    2. import java.net.InetSocketAddress;
    3. import java.nio.channels.Selector;
    4. import java.nio.channels.ServerSocketChannel;
    5. import java.nio.channels.SelectionKey;
    6. import java.util.Iterator;
    7. import java.util.Set;
    8. public class SelectorExample {
    9. public static void main(String[] args) throws IOException {
    10. // 创建选择器
    11. Selector selector = Selector.open();
    12. // 创建ServerSocketChannel并绑定端口
    13. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    14. serverSocketChannel.socket().bind(new InetSocketAddress(8888));
    15. serverSocketChannel.configureBlocking(false);
    16. // 将通道注册到选择器上,并指定感兴趣的事件为接收连接
    17. serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    18. while (true) {
    19. // 阻塞等待就绪的通道,最多等待10秒
    20. int readyChannels = selector.select(10000);
    21. if (readyChannels == 0) {
    22. continue;
    23. }
    24. // 获取已经就绪的通道集合
    25. Set selectedKeys = selector.selectedKeys();
    26. Iterator keyIterator = selectedKeys.iterator();
    27. while (keyIterator.hasNext()) {
    28. SelectionKey key = keyIterator.next();
    29. if (key.isAcceptable()) {
    30. // 处理连接事件
    31. // ...
    32. }
    33. if (key.isReadable()) {
    34. // 处理读事件
    35. // ...
    36. }
    37. if (key.isWritable()) {
    38. // 处理写事件
    39. // ...
    40. }
    41. // 处理完事件后,需要从已选择键集合中移除
    42. keyIterator.remove();
    43. }
    44. }
    45. }
    46. }


    上述示例演示了如何使用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来方便地读取和写入数据。通过合理使用缓冲区,可以提高数据读写的效率和性能。

    1. import java.io.IOException;
    2. import java.net.InetSocketAddress;
    3. import java.nio.ByteBuffer;
    4. import java.nio.channels.SelectionKey;
    5. import java.nio.channels.Selector;
    6. import java.nio.channels.ServerSocketChannel;
    7. import java.nio.channels.SocketChannel;
    8. public class NioServer {
    9. public static void main(String[] args) throws IOException {
    10. ServerSocketChannel serverChannel = ServerSocketChannel.open();
    11. serverChannel.socket().bind(new InetSocketAddress(9999));
    12. serverChannel.configureBlocking(false);
    13. Selector selector = Selector.open();
    14. serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    15. while (true) {
    16. if (selector.select() > 0) {
    17. for (SelectionKey key : selector.selectedKeys()) {
    18. if (key.isAcceptable()) {
    19. SocketChannel clientChannel = serverChannel.accept();
    20. clientChannel.configureBlocking(false);
    21. clientChannel.register(selector, SelectionKey.OP_READ);
    22. } else if (key.isReadable()) {
    23. SocketChannel clientChannel = (SocketChannel) key.channel();
    24. ByteBuffer buffer = ByteBuffer.allocate(1024);
    25. int bytesRead = clientChannel.read(buffer);
    26. if (bytesRead > 0) {
    27. System.out.println("Received from client: " + new String(buffer.array(), 0, bytesRead));
    28. buffer.flip();
    29. clientChannel.write(buffer);
    30. }
    31. }
    32. }
    33. selector.selectedKeys().clear();
    34. }
    35. }
    36. }
    37. }
    38. // 在这个例子中,服务器端使用 ServerSocketChannel 监听端口,当有客户端连接时,接受连接并使用 SocketChannel 与客户端通信。
    39. // Selector 用于监听多个客户端连接,并使用 SelectionKey 来管理每个连接的状态。
    40. // 客户端使用 SocketChannel 连接到服务器,然后使用 ByteBuffer 发送和接收数据。
    41. // 请注意,这个例子仅用于演示 Java NIO 的基本使用,在实际应用中,您可能需要处理更多的细节,例如异常处理、线程管理等。

     

    1. import java.io.IOException;
    2. import java.net.InetSocketAddress;
    3. import java.nio.ByteBuffer;
    4. import java.nio.channels.SocketChannel;
    5. public class NioClient {
    6. public static void main(String[] args) throws IOException {
    7. SocketChannel clientChannel = SocketChannel.open();
    8. clientChannel.connect(new InetSocketAddress("localhost", 9999));
    9. ByteBuffer buffer = ByteBuffer.allocate(1024);
    10. buffer.put("Hello, server!".getBytes());
    11. buffer.flip();
    12. clientChannel.write(buffer);
    13. buffer.clear();
    14. int bytesRead = clientChannel.read(buffer);
    15. if (bytesRead > 0) {
    16. System.out.println("Received from server: " + new String(buffer.array(), 0, bytesRead));
    17. }
    18. clientChannel.close();
    19. }
    20. }

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

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

  • 相关阅读:
    11.数组的分类和定义
    网络安全复习大纲wcf
    .net 姓名转拼音码、五笔码
    Python基础教学之五:异常处理与文件操作——让程序更健壮
    【实习之velocity】
    编译原理-总概
    【高效开发工具系列】Windows 系统下将 Windows 键盘的 ctrl 和 alt 互换
    Python 中的 with 语句用法和 Pytorch 中的 with torch.no_grad() 解析
    内网可达网段探测netspy- Mac环境
    修改docker ip网段
  • 原文地址:https://blog.csdn.net/book_dw5189/article/details/139051108