当前位置:首页 >百科 >从Java IO到Java NIO:如何理解阻塞和非阻塞I/O的区别? 理解当进行输入/输出操作时

从Java IO到Java NIO:如何理解阻塞和非阻塞I/O的区别? 理解当进行输入/输出操作时

2024-06-28 19:27:28 [百科] 来源:避面尹邢网

从Java IO到Java NIO:如何理解阻塞和非阻塞I/O的到的区区别?

作者:你的老师父 开发 后端 Java NIO是非阻塞的,因为它基于选择器和通道实现了非阻塞I/O,何和非支持同时处理多个通道的理解I/O事件,从而提高了I/O操作的阻塞阻塞效率和响应性能。相比之下,到的区传统的何和非Java IO(也称为IO流)是阻塞的,因为它只能同时处理一个输入/输出流,理解当进行输入/输出操作时,阻塞阻塞线程会一直阻塞,到的区直到数据传输完成或者发生异常。何和非

Java NIO实现非阻塞I/O

在Java中,理解阻塞I/O(Blocking I/O)和非阻塞I/O(Non-blocking I/O)是阻塞阻塞两种不同的I/O模式。

从Java IO到Java NIO:如何理解阻塞和非阻塞I/O的区别? 理解当进行输入/输出操作时

阻塞I/O模式下,到的区当应用程序进行输入/输出操作时,何和非线程会一直阻塞,理解直到数据传输完成或者发生异常。在此期间,线程无法执行其他任务,因此阻塞I/O模式具有较低的效率和响应性能。

从Java IO到Java NIO:如何理解阻塞和非阻塞I/O的区别? 理解当进行输入/输出操作时

非阻塞I/O模式下,当应用程序进行输入/输出操作时,线程会立即返回,并且不会等待数据传输完成。在此期间,线程可以执行其他任务,因此非阻塞I/O模式具有较高的效率和响应性能。

从Java IO到Java NIO:如何理解阻塞和非阻塞I/O的区别? 理解当进行输入/输出操作时

Java NIO中的非阻塞I/O是基于选择器(Selector)和通道(Channel)的。选择器可以监听多个通道上的I/O事件,并在有事件发生时通知应用程序,从而实现非阻塞I/O操作。通道则是用于输入/输出操作的对象,可以是文件通道或网络通道。

Java NIO是非阻塞的,因为它基于选择器和通道实现了非阻塞I/O,支持同时处理多个通道的I/O事件,从而提高了I/O操作的效率和响应性能。相比之下,传统的Java IO(也称为IO流)是阻塞的,因为它只能同时处理一个输入/输出流,当进行输入/输出操作时,线程会一直阻塞,直到数据传输完成或者发生异常。

1、创建通道

通道是Java NIO中用于输入/输出操作的对象,可以通过SocketChannel、ServerSocketChannel、DatagramChannel等创建网络通道,或者通过FileChannel创建文件通道。在这里,我们以SocketChannel为例创建网络通道。

SocketChannel channel = SocketChannel.open();

2、将通道设置为非阻塞模式

通过调用通道的configureBlocking(false)方法,将通道设置为非阻塞模式。在非阻塞模式下,通道的读取和写入操作不会阻塞线程,而是立即返回。

channel.configureBlocking(false);

3、创建选择器

选择器是Java NIO中用于监听多个通道的I/O事件的对象,用于实现非阻塞I/O。可以通过Selector.open()方法创建选择器。

Selector selector = Selector.open();

4、将通道注册到选择器上

通过调用通道的register()方法,将通道注册到选择器上,并指定要监听的事件类型,例如读取事件、写入事件、连接事件、接受事件等。在这里,我们注册了读取事件。

channel.register(selector, SelectionKey.OP_READ);

5、轮询选择器

通过调用选择器的select()方法,轮询选择器上注册的通道,当有通道上的I/O事件就绪时,select()方法会返回就绪的通道数量。

while (true) {     selector.select();    Set<SelectionKey> selectedKeys = selector.selectedKeys();    Iterator<SelectionKey> keyIterator = selectedKeys.iterator();    while (keyIterator.hasNext()) {         SelectionKey key = keyIterator.next();        // 处理就绪的通道        keyIterator.remove();    }}

6、处理就绪的通道

通过调用选择器的selectedKeys()方法,获取所有就绪的通道,并进行相应的读取或写入操作。在这里,我们实现了从通道读取数据的操作。

while (true) {     selector.select();    Set<SelectionKey> selectedKeys = selector.selectedKeys();    Iterator<SelectionKey> keyIterator = selectedKeys.iterator();    while (keyIterator.hasNext()) {         SelectionKey key = keyIterator.next();        if (key.isReadable()) {             SocketChannel channel = (SocketChannel) key.channel();            ByteBuffer buffer = ByteBuffer.allocate(1024);            int bytesRead = channel.read(buffer);            while (bytesRead > 0) {                 buffer.flip();                while (buffer.hasRemaining()) {                     System.out.print((char) buffer.get());                }                buffer.clear();                bytesRead = channel.read(buffer);            }            if (bytesRead == -1) {                 channel.close();            }        }        keyIterator.remove();    }}

需要注意的是,在非阻塞I/O模式下,读取和写入操作通常需要多次调用,直到完整的数据传输完成。在读取操作中,需要将数据从通道读取到缓冲区,并判断缓冲区中是否已经读取完毕。

此外,在非阻塞I/O模式下,发生异常的可能性比较高,因此需要进行异常处理。可以通过选择器的selectedKeys()方法和SelectionKey的readyOps()方法,判断通道是否出现异常,并进行相应的处理。

以下是完整的示例代码。在这个例子中,我们使用了一个简单的Echo服务器,将客户端发送的消息原样返回。

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;import java.util.Iterator;import java.util.Set;public class NonBlockingServer {     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);        System.out.println("Server started on port 9999");        while (true) {             selector.select();            Set<SelectionKey> selectedKeys = selector.selectedKeys();            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();            while (keyIterator.hasNext()) {                 SelectionKey key = keyIterator.next();                if (key.isAcceptable()) {                     // 处理连接事件                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();                    SocketChannel clientChannel = channel.accept();                    clientChannel.configureBlocking(false);                    clientChannel.register(selector, SelectionKey.OP_READ);                    System.out.println("Client connected: " + clientChannel.getRemoteAddress());                } else if (key.isReadable()) {                     // 处理读取事件                    SocketChannel channel = (SocketChannel) key.channel();                    ByteBuffer buffer = ByteBuffer.allocate(1024);                    int bytesRead = channel.read(buffer);                    while (bytesRead > 0) {                         buffer.flip();                        while (buffer.hasRemaining()) {                             channel.write(buffer);                        }                        buffer.clear();                        bytesRead = channel.read(buffer);                    }                    if (bytesRead == -1) {                         channel.close();                    }                }                keyIterator.remove();            }        }    }}

问题:selector.select()是阻塞,为什么还说NIO是非阻塞的呢?

selector.select()方法确实会阻塞,直到有至少一个通道准备好进行I/O操作或者等待超时或中断。但是,需要注意的是,这种阻塞只会影响当前的线程,不会影响应用程序的其他线程。

在服务端线程调用选择器的select()方法时,只有当前服务端线程会被阻塞,而不是客户端线程。

客户端的阻塞和非阻塞I/O操作取决于具体的实现。对于阻塞I/O模式,客户端线程在进行输入/输出操作时,会一直阻塞,直到数据传输完成或者发生异常。对于非阻塞I/O模式,客户端线程在进行输入/输出操作时,会立即返回,并且不会等待数据传输完成。在此期间,客户端线程可以执行其他任务。

因此,Java NIO仍然可以称为非阻塞I/O。

Java NIO提供了一种基于事件驱动的I/O模型,应用程序使用选择器(Selector)来注册通道(Channel)上的I/O事件,并在有事件发生时进行相应的处理。在选择器上调用select()方法会阻塞当前线程,直到至少有一个通道上注册的事件发生,此时select()方法会返回,应用程序可以通过selectedKeys()方法获取就绪的事件。由于选择器可以同时监听多个通道,因此Java NIO可以同时处理多个通道上的I/O事件,从而提高了I/O操作的效率和响应性能。

需要注意的是,虽然选择器的select()方法会阻塞当前线程,但是可以通过调用选择器的wakeup()方法中断阻塞,使得select()方法立即返回。此外,可以在选择器上设置超时时间,使得select()方法在指定时间内返回,避免长时间的无限阻塞。

实战Java NIO中实现文件I/O(File I/O)和网络I/O(Network I/O)

文件I/O(File I/O)

Java NIO中的文件I/O是通过FileChannel来实现的。FileChannel类提供了读取和写入文件的方法,而ByteBuffer类则用于存储读取和写入的数据。

以下是实现文件I/O的详细步骤:

步骤1:获取FileChannel实例

在进行文件I/O之前,需要先获取FileChannel实例。可以通过FileInputStream或FileOutputStream来获取FileChannel实例,例如:

FileInputStream fileInputStream = new FileInputStream("file.txt");FileChannel fileChannel = fileInputStream.getChannel();

步骤2:创建ByteBuffer

在进行文件I/O之前,需要先创建ByteBuffer实例,用于存储读取和写入的数据。可以通过ByteBuffer的allocate方法创建ByteBuffer实例,例如:

ByteBuffer buffer = ByteBuffer.allocate(1024);

步骤3:读取文件数据

(1)从FileChannel中读取数据

可以通过FileChannel的read方法从文件中读取数据,并将数据存储到ByteBuffer中。read方法有两个重载版本:

int read(ByteBuffer dst) throws IOException;long read(ByteBuffer[] dsts, int offset, int length) throws IOException;

第一个版本的read方法将数据读取到单个ByteBuffer中,返回值为读取的字节数。如果返回值为-1,表示已经读取到了文件的末尾。

第二个版本的read方法将数据读取到多个ByteBuffer中,返回值为读取的字节数。如果返回值为-1,表示已经读取到了文件的末尾。

以下是使用第一个版本read方法的示例代码:

int bytesRead = fileChannel.read(buffer);while (bytesRead != -1) {     buffer.flip();    while (buffer.hasRemaining()) {         System.out.print((char) buffer.get());    }    buffer.clear();    bytesRead = fileChannel.read(buffer);}

上述代码首先通过FileChannel的read方法将数据读取到ByteBuffer中,并返回读取的字节数。随后,通过flip方法将ByteBuffer从写模式切换为读模式,并通过get方法读取ByteBuffer中的数据。当ByteBuffer中的数据被读取完毕后,通过clear方法将ByteBuffer从读模式切换为写模式,并再次调用FileChannel的read方法读取文件中的数据,直到文件中的所有数据被读取完毕。

(2)向FileChannel中写入数据

可以通过FileChannel的write方法向文件中写入数据,例如:

byte[] data = "Hello, World!".getBytes();ByteBuffer buffer = ByteBuffer.wrap(data);int bytesWritten = fileChannel.write(buffer);

上述代码首先将数据存储到ByteBuffer中,随后调用FileChannel的write方法将数据写入到文件中。

步骤4:关闭FileChannel

在使用完FileChannel后,需要调用其close方法关闭FileChannel,例如:

fileChannel.close();

完整的代码示例:

import java.io.FileInputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;public class FileIODemo {     public static void main(String[] args) throws IOException {         FileInputStream fileInputStream = new FileInputStream("file.txt");        FileChannel fileChannel = fileInputStream.getChannel();        ByteBuffer buffer = ByteBuffer.allocate(1024);        int bytesRead = fileChannel.read(buffer);        while (bytesRead != -1) {             buffer.flip();            while (buffer.hasRemaining()) {                 System.out.print((char) buffer.get());            }            buffer.clear();            bytesRead = fileChannel.read(buffer);        }        fileChannel.close();    }}

网络I/O(Network I/O)

Java NIO中的网络I/O是通过SocketChannel和ServerSocketChannel来实现的,它们分别用于客户端和服务端的网络通信。

以下是实现网络I/O的详细步骤:

步骤1:获取SocketChannel或ServerSocketChannel实例

在进行网络I/O之前,需要先获取SocketChannel或ServerSocketChannel实例。可以通过SocketChannel或ServerSocketChannel的open方法获取相应的实例,例如:

SocketChannel socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress("www.example.com", 80));

或:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress(8080));

步骤2:创建ByteBuffer

在进行网络I/O之前,需要先创建ByteBuffer实例,用于存储读取和写入的数据。可以通过ByteBuffer的allocate方法创建ByteBuffer实例,例如:

ByteBuffer buffer = ByteBuffer.allocate(1024);

步骤3:读取网络数据

(1)从SocketChannel中读取数据

可以通过SocketChannel的read方法从网络中读取数据,并将数据存储到ByteBuffer中。read方法的用法与文件I/O中的read方法相同,这里不再赘述。

以下是使用SocketChannel的read方法的示例代码:

int bytesRead = socketChannel.read(buffer);while (bytesRead != -1) {     buffer.flip();    while (buffer.hasRemaining()) {         System.out.print((char) buffer.get());    }    buffer.clear();    bytesRead = socketChannel.read(buffer);}

上述代码首先通过SocketChannel的read方法将数据读取到ByteBuffer中,并返回读取的字节数。随后,通过flip方法将ByteBuffer从写模式切换为读模式,并通过get方法读取ByteBuffer中的数据。当ByteBuffer中的数据被读取完毕后,通过clear方法将ByteBuffer从读模式切换为写模式,并再次调用SocketChannel的read方法读取网络中的数据,直到网络中的所有数据被读取完毕。

(2)向SocketChannel中写入数据

可以通过SocketChannel的write方法向网络中写入数据,例如:

byte[] data = "Hello, World!".getBytes();ByteBuffer buffer = ByteBuffer.wrap(data);int bytesWritten = socketChannel.write(buffer);

上述代码首先将数据存储到ByteBuffer中,随后调用SocketChannel的write方法将数据写入到网络中。

步骤4:关闭SocketChannel或ServerSocketChannel

在使用完SocketChannel或ServerSocketChannel后,需要调用其close方法关闭SocketChannel或ServerSocketChannel,例如:

socketChannel.close();

或:

serverSocketChannel.close();

:完整的代码示例:

import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SocketChannel;public class NetworkIODemo {     public static void main(String[] args) throws IOException {         SocketChannel socketChannel = SocketChannel.open();        socketChannel.connect(new InetSocketAddress("www.example.com", 80));        ByteBuffer buffer = ByteBuffer.allocate(1024);        int bytesRead = socketChannel.read(buffer);        while (bytesRead != -1) {             buffer.flip();            while (buffer.hasRemaining()) {                 System.out.print((char) buffer.get());            }            buffer.clear();            bytesRead = socketChannel.read(buffer);        }        socketChannel.close();    }}
责任编辑:姜华 来源: 今日头条 Java NIO非阻塞阻塞

(责任编辑:热点)

    推荐文章
    热点阅读