自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從Java IO到Java NIO:如何理解阻塞和非阻塞I/O的區(qū)別?

開發(fā) 后端
Java NIO是非阻塞的,因?yàn)樗谶x擇器和通道實(shí)現(xiàn)了非阻塞I/O,支持同時(shí)處理多個(gè)通道的I/O事件,從而提高了I/O操作的效率和響應(yīng)性能。相比之下,傳統(tǒng)的Java IO(也稱為IO流)是阻塞的,因?yàn)樗荒芡瑫r(shí)處理一個(gè)輸入/輸出流,當(dāng)進(jìn)行輸入/輸出操作時(shí),線程會(huì)一直阻塞,直到數(shù)據(jù)傳輸完成或者發(fā)生異常。

Java NIO實(shí)現(xiàn)非阻塞I/O

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

阻塞I/O模式下,當(dāng)應(yīng)用程序進(jìn)行輸入/輸出操作時(shí),線程會(huì)一直阻塞,直到數(shù)據(jù)傳輸完成或者發(fā)生異常。在此期間,線程無法執(zhí)行其他任務(wù),因此阻塞I/O模式具有較低的效率和響應(yīng)性能。

非阻塞I/O模式下,當(dāng)應(yīng)用程序進(jìn)行輸入/輸出操作時(shí),線程會(huì)立即返回,并且不會(huì)等待數(shù)據(jù)傳輸完成。在此期間,線程可以執(zhí)行其他任務(wù),因此非阻塞I/O模式具有較高的效率和響應(yīng)性能。

Java NIO中的非阻塞I/O是基于選擇器(Selector)和通道(Channel)的。選擇器可以監(jiān)聽多個(gè)通道上的I/O事件,并在有事件發(fā)生時(shí)通知應(yīng)用程序,從而實(shí)現(xiàn)非阻塞I/O操作。通道則是用于輸入/輸出操作的對(duì)象,可以是文件通道或網(wǎng)絡(luò)通道。

Java NIO是非阻塞的,因?yàn)樗谶x擇器和通道實(shí)現(xiàn)了非阻塞I/O,支持同時(shí)處理多個(gè)通道的I/O事件,從而提高了I/O操作的效率和響應(yīng)性能。相比之下,傳統(tǒng)的Java IO(也稱為IO流)是阻塞的,因?yàn)樗荒芡瑫r(shí)處理一個(gè)輸入/輸出流,當(dāng)進(jìn)行輸入/輸出操作時(shí),線程會(huì)一直阻塞,直到數(shù)據(jù)傳輸完成或者發(fā)生異常。

1、創(chuàng)建通道

通道是Java NIO中用于輸入/輸出操作的對(duì)象,可以通過SocketChannel、ServerSocketChannel、DatagramChannel等創(chuàng)建網(wǎng)絡(luò)通道,或者通過FileChannel創(chuàng)建文件通道。在這里,我們以SocketChannel為例創(chuàng)建網(wǎng)絡(luò)通道。

SocketChannel channel = SocketChannel.open();

2、將通道設(shè)置為非阻塞模式

通過調(diào)用通道的configureBlocking(false)方法,將通道設(shè)置為非阻塞模式。在非阻塞模式下,通道的讀取和寫入操作不會(huì)阻塞線程,而是立即返回。

channel.configureBlocking(false);

3、創(chuàng)建選擇器

選擇器是Java NIO中用于監(jiān)聽多個(gè)通道的I/O事件的對(duì)象,用于實(shí)現(xiàn)非阻塞I/O。可以通過Selector.open()方法創(chuàng)建選擇器。

Selector selector = Selector.open();

4、將通道注冊(cè)到選擇器上

通過調(diào)用通道的register()方法,將通道注冊(cè)到選擇器上,并指定要監(jiān)聽的事件類型,例如讀取事件、寫入事件、連接事件、接受事件等。在這里,我們注冊(cè)了讀取事件。

channel.register(selector, SelectionKey.OP_READ);

5、輪詢選擇器

通過調(diào)用選擇器的select()方法,輪詢選擇器上注冊(cè)的通道,當(dāng)有通道上的I/O事件就緒時(shí),select()方法會(huì)返回就緒的通道數(shù)量。

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

6、處理就緒的通道

通過調(diào)用選擇器的selectedKeys()方法,獲取所有就緒的通道,并進(jìn)行相應(yīng)的讀取或?qū)懭氩僮鳌T谶@里,我們實(shí)現(xiàn)了從通道讀取數(shù)據(jù)的操作。

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模式下,讀取和寫入操作通常需要多次調(diào)用,直到完整的數(shù)據(jù)傳輸完成。在讀取操作中,需要將數(shù)據(jù)從通道讀取到緩沖區(qū),并判斷緩沖區(qū)中是否已經(jīng)讀取完畢。

此外,在非阻塞I/O模式下,發(fā)生異常的可能性比較高,因此需要進(jìn)行異常處理??梢酝ㄟ^選擇器的selectedKeys()方法和SelectionKey的readyOps()方法,判斷通道是否出現(xiàn)異常,并進(jìn)行相應(yīng)的處理。

以下是完整的示例代碼。在這個(gè)例子中,我們使用了一個(gè)簡(jiǎn)單的Echo服務(wù)器,將客戶端發(fā)送的消息原樣返回。

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 {
        // 創(chuàng)建服務(wù)器套接字通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.socket().bind(new InetSocketAddress(9999));
        serverChannel.configureBlocking(false);

        // 創(chuàng)建選擇器
        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()方法確實(shí)會(huì)阻塞,直到有至少一個(gè)通道準(zhǔn)備好進(jìn)行I/O操作或者等待超時(shí)或中斷。但是,需要注意的是,這種阻塞只會(huì)影響當(dāng)前的線程,不會(huì)影響應(yīng)用程序的其他線程。

在服務(wù)端線程調(diào)用選擇器的select()方法時(shí),只有當(dāng)前服務(wù)端線程會(huì)被阻塞,而不是客戶端線程。

客戶端的阻塞和非阻塞I/O操作取決于具體的實(shí)現(xiàn)。對(duì)于阻塞I/O模式,客戶端線程在進(jìn)行輸入/輸出操作時(shí),會(huì)一直阻塞,直到數(shù)據(jù)傳輸完成或者發(fā)生異常。對(duì)于非阻塞I/O模式,客戶端線程在進(jìn)行輸入/輸出操作時(shí),會(huì)立即返回,并且不會(huì)等待數(shù)據(jù)傳輸完成。在此期間,客戶端線程可以執(zhí)行其他任務(wù)。

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

Java NIO提供了一種基于事件驅(qū)動(dòng)的I/O模型,應(yīng)用程序使用選擇器(Selector)來注冊(cè)通道(Channel)上的I/O事件,并在有事件發(fā)生時(shí)進(jìn)行相應(yīng)的處理。在選擇器上調(diào)用select()方法會(huì)阻塞當(dāng)前線程,直到至少有一個(gè)通道上注冊(cè)的事件發(fā)生,此時(shí)select()方法會(huì)返回,應(yīng)用程序可以通過selectedKeys()方法獲取就緒的事件。由于選擇器可以同時(shí)監(jiān)聽多個(gè)通道,因此Java NIO可以同時(shí)處理多個(gè)通道上的I/O事件,從而提高了I/O操作的效率和響應(yīng)性能。

需要注意的是,雖然選擇器的select()方法會(huì)阻塞當(dāng)前線程,但是可以通過調(diào)用選擇器的wakeup()方法中斷阻塞,使得select()方法立即返回。此外,可以在選擇器上設(shè)置超時(shí)時(shí)間,使得select()方法在指定時(shí)間內(nèi)返回,避免長(zhǎng)時(shí)間的無限阻塞。

實(shí)戰(zhàn)Java NIO中實(shí)現(xiàn)文件I/O(File I/O)和網(wǎng)絡(luò)I/O(Network I/O)

文件I/O(File I/O)

Java NIO中的文件I/O是通過FileChannel來實(shí)現(xiàn)的。FileChannel類提供了讀取和寫入文件的方法,而ByteBuffer類則用于存儲(chǔ)讀取和寫入的數(shù)據(jù)。

以下是實(shí)現(xiàn)文件I/O的詳細(xì)步驟:

步驟1:獲取FileChannel實(shí)例

在進(jìn)行文件I/O之前,需要先獲取FileChannel實(shí)例??梢酝ㄟ^FileInputStream或FileOutputStream來獲取FileChannel實(shí)例,例如:

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

步驟2:創(chuàng)建ByteBuffer

在進(jìn)行文件I/O之前,需要先創(chuàng)建ByteBuffer實(shí)例,用于存儲(chǔ)讀取和寫入的數(shù)據(jù)??梢酝ㄟ^ByteBuffer的allocate方法創(chuàng)建ByteBuffer實(shí)例,例如:

ByteBuffer buffer = ByteBuffer.allocate(1024);

步驟3:讀取文件數(shù)據(jù)

(1)從FileChannel中讀取數(shù)據(jù)

可以通過FileChannel的read方法從文件中讀取數(shù)據(jù),并將數(shù)據(jù)存儲(chǔ)到ByteBuffer中。read方法有兩個(gè)重載版本:

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

第一個(gè)版本的read方法將數(shù)據(jù)讀取到單個(gè)ByteBuffer中,返回值為讀取的字節(jié)數(shù)。如果返回值為-1,表示已經(jīng)讀取到了文件的末尾。

第二個(gè)版本的read方法將數(shù)據(jù)讀取到多個(gè)ByteBuffer中,返回值為讀取的字節(jié)數(shù)。如果返回值為-1,表示已經(jīng)讀取到了文件的末尾。

以下是使用第一個(gè)版本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方法將數(shù)據(jù)讀取到ByteBuffer中,并返回讀取的字節(jié)數(shù)。隨后,通過flip方法將ByteBuffer從寫模式切換為讀模式,并通過get方法讀取ByteBuffer中的數(shù)據(jù)。當(dāng)ByteBuffer中的數(shù)據(jù)被讀取完畢后,通過clear方法將ByteBuffer從讀模式切換為寫模式,并再次調(diào)用FileChannel的read方法讀取文件中的數(shù)據(jù),直到文件中的所有數(shù)據(jù)被讀取完畢。

(2)向FileChannel中寫入數(shù)據(jù)

可以通過FileChannel的write方法向文件中寫入數(shù)據(jù),例如:

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

上述代碼首先將數(shù)據(jù)存儲(chǔ)到ByteBuffer中,隨后調(diào)用FileChannel的write方法將數(shù)據(jù)寫入到文件中。

步驟4:關(guān)閉FileChannel

在使用完FileChannel后,需要調(diào)用其close方法關(guān)閉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();
    }
}

網(wǎng)絡(luò)I/O(Network I/O)

Java NIO中的網(wǎng)絡(luò)I/O是通過SocketChannel和ServerSocketChannel來實(shí)現(xiàn)的,它們分別用于客戶端和服務(wù)端的網(wǎng)絡(luò)通信。

以下是實(shí)現(xiàn)網(wǎng)絡(luò)I/O的詳細(xì)步驟:

步驟1:獲取SocketChannel或ServerSocketChannel實(shí)例

在進(jìn)行網(wǎng)絡(luò)I/O之前,需要先獲取SocketChannel或ServerSocketChannel實(shí)例。可以通過SocketChannel或ServerSocketChannel的open方法獲取相應(yīng)的實(shí)例,例如:

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

或:

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

步驟2:創(chuàng)建ByteBuffer

在進(jìn)行網(wǎng)絡(luò)I/O之前,需要先創(chuàng)建ByteBuffer實(shí)例,用于存儲(chǔ)讀取和寫入的數(shù)據(jù)??梢酝ㄟ^ByteBuffer的allocate方法創(chuàng)建ByteBuffer實(shí)例,例如:

ByteBuffer buffer = ByteBuffer.allocate(1024);

步驟3:讀取網(wǎng)絡(luò)數(shù)據(jù)

(1)從SocketChannel中讀取數(shù)據(jù)

可以通過SocketChannel的read方法從網(wǎng)絡(luò)中讀取數(shù)據(jù),并將數(shù)據(jù)存儲(chǔ)到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方法將數(shù)據(jù)讀取到ByteBuffer中,并返回讀取的字節(jié)數(shù)。隨后,通過flip方法將ByteBuffer從寫模式切換為讀模式,并通過get方法讀取ByteBuffer中的數(shù)據(jù)。當(dāng)ByteBuffer中的數(shù)據(jù)被讀取完畢后,通過clear方法將ByteBuffer從讀模式切換為寫模式,并再次調(diào)用SocketChannel的read方法讀取網(wǎng)絡(luò)中的數(shù)據(jù),直到網(wǎng)絡(luò)中的所有數(shù)據(jù)被讀取完畢。

(2)向SocketChannel中寫入數(shù)據(jù)

可以通過SocketChannel的write方法向網(wǎng)絡(luò)中寫入數(shù)據(jù),例如:

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

上述代碼首先將數(shù)據(jù)存儲(chǔ)到ByteBuffer中,隨后調(diào)用SocketChannel的write方法將數(shù)據(jù)寫入到網(wǎng)絡(luò)中。

步驟4:關(guān)閉SocketChannel或ServerSocketChannel

在使用完SocketChannel或ServerSocketChannel后,需要調(diào)用其close方法關(guān)閉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();
    }
}
責(zé)任編輯:姜華 來源: 今日頭條
相關(guān)推薦

2012-02-22 21:15:41

unixIO阻塞

2024-11-26 10:37:19

2018-03-28 08:52:53

阻塞非阻塞I

2011-12-07 17:17:02

JavaNIO

2011-12-08 10:12:34

JavaNIO

2021-06-04 18:14:15

阻塞非阻塞tcp

2023-12-06 07:28:47

阻塞IO異步IO

2015-07-03 10:12:04

編程同步非阻塞

2021-10-13 06:49:15

網(wǎng)絡(luò) IO

2023-12-13 09:45:49

模型程序

2023-08-07 08:52:03

Java多路復(fù)用機(jī)制

2024-06-19 10:26:36

非阻塞IO客戶端

2012-10-10 10:00:27

同步異步開發(fā)Java

2021-02-27 16:08:17

Java異步非阻塞

2021-03-04 08:34:55

同步阻塞非阻塞

2022-12-08 09:10:11

I/O模型Java

2022-06-22 08:16:29

異步非阻塞框架

2017-03-01 16:40:12

Linux驅(qū)動(dòng)技術(shù)設(shè)備阻塞

2019-07-23 11:01:57

Python同步異步

2019-10-18 08:22:43

BIONIOAIO
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)