欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

目錄

柚子快報(bào)激活碼778899分享:java NIO編程

柚子快報(bào)激活碼778899分享:java NIO編程

http://yzkb.51969.com/

目錄

1、什么是NIO編程?

為什么說(shuō)Java NIO是非阻塞的?

2、Java NIO 通道(Channel)詳解

如何獲取Channel對(duì)象?

3、Java NIO 緩沖區(qū)(Buffer)詳解

(1)獲取緩沖區(qū)對(duì)象

(2)將數(shù)據(jù)寫入Buffer以及從Buffer讀取數(shù)據(jù)

(3)通道Channel和緩沖區(qū)Buffer的相互讀寫

(4)圖解緩沖區(qū)Buffer

4、Java NIO 選擇器/Selector詳解

(1)將channel注冊(cè)到Selector上

(2)為什么說(shuō) select() 方法是阻塞的?

(3)Selector 的 selectedKeys() 方法

5、為什么說(shuō)Java NIO是事件驅(qū)動(dòng)的?

6、NIO編程完整的代碼示例

1、什么是NIO編程?

????????NIO(New I/O)是Java中一種提供了非阻塞式I/O操作的編程模型。它引入了一組新的Java類,用于取代傳統(tǒng)的Java I/O類(如InputStream和OutputStream),以提供更高效、更靈活的I/O操作。//?部分NIO API實(shí)際上是阻塞的,例如File API

????????Java NIO允許執(zhí)行非阻塞的IO。例如,線程可以請(qǐng)求通道將數(shù)據(jù)讀入緩沖區(qū)。當(dāng)通道將數(shù)據(jù)讀入緩沖區(qū)時(shí),線程可以做其他事情。一旦數(shù)據(jù)被讀入緩沖區(qū),線程就可以繼續(xù)處理它。將數(shù)據(jù)寫入通道也是如此。// 非阻塞的核心在于選擇器

????????NIO的核心組件包括以下幾個(gè)方面:

通道(Channel):通道是數(shù)據(jù)傳輸?shù)妮d體,類似于傳統(tǒng)的流(Stream)。不同的是,通道可以同時(shí)支持讀和寫操作,而流一般只能單向傳輸數(shù)據(jù)。通道可以連接到文件、套接字等不同的I/O源。//數(shù)據(jù)總是從通道讀入緩沖區(qū),或從緩沖區(qū)寫入通道緩沖區(qū)(Buffer):緩沖區(qū)是NIO中用于存儲(chǔ)數(shù)據(jù)的對(duì)象。數(shù)據(jù)從通道讀入緩沖區(qū),或者從緩沖區(qū)寫入通道。緩沖區(qū)可以提供更高效的數(shù)據(jù)處理,因?yàn)樗梢耘孔x寫數(shù)據(jù),而不需要逐個(gè)字節(jié)地操作。選擇器(Selector):選擇器是NIO的一個(gè)關(guān)鍵概念,用于實(shí)現(xiàn)多路復(fù)用。它允許單個(gè)線程監(jiān)視多個(gè)通道的I/O事件,從而提高系統(tǒng)資源的利用率。選擇器會(huì)不斷輪詢注冊(cè)在其上的通道,一旦某個(gè)通道就緒(有數(shù)據(jù)可讀或可寫),就會(huì)通知程序進(jìn)行相應(yīng)的處理。

????????下面是一個(gè)Thread使用Selector處理3個(gè)Channel的示例:

????????要使用Selector,首先需要注冊(cè)Channel,然后調(diào)用它的select()方法。select()方法將阻塞,直到為其中一個(gè)已注冊(cè)通道準(zhǔn)備好事件(Event)為止。select()方法返回后,線程就可以處理這些事件。

為什么說(shuō)Java NIO是非阻塞的?

????????Java NIO提供了一種非阻塞的I/O操作模型,相比傳統(tǒng)的阻塞式I/O模型,Java NIO允許應(yīng)用程序在進(jìn)行I/O操作時(shí)不需要等待數(shù)據(jù)的到達(dá)或發(fā)送完成,而可以繼續(xù)執(zhí)行其他任務(wù)。

????????在傳統(tǒng)的阻塞式I/O模型中,當(dāng)一個(gè)線程執(zhí)行一個(gè)I/O操作(如讀取文件或網(wǎng)絡(luò)數(shù)據(jù))時(shí),它將被阻塞,直到操作完成或發(fā)生錯(cuò)誤。這意味著在阻塞模型下,一個(gè)線程只能處理一個(gè)I/O操作,如果有多個(gè)I/O操作需要處理,就需要使用多個(gè)線程,增加了線程的開(kāi)銷和管理復(fù)雜性。

????????而Java NIO使用了非阻塞的I/O操作模型。它引入了Channel(通道)和Selector(選擇器)的概念,使得應(yīng)用程序可以注冊(cè)多個(gè)Channel到一個(gè)Selector上,并通過(guò)Selector來(lái)監(jiān)控這些Channel的狀態(tài)。Selector可以輪詢已注冊(cè)的Channel,當(dāng)某個(gè)Channel滿足I/O事件時(shí)(如可讀、可寫等),就會(huì)通知應(yīng)用程序進(jìn)行相應(yīng)的處理。

????????在非阻塞模型下,一個(gè)線程可以同時(shí)處理多個(gè)Channel的I/O操作,而不需要為每個(gè)Channel創(chuàng)建一個(gè)單獨(dú)的線程。這種方式可以大大提高系統(tǒng)的并發(fā)性能和資源利用率。

????????此外,Java NIO還提供了基于事件驅(qū)動(dòng)的異步I/O操作模型(AIO,Asynchronous I/O),通過(guò)使用Future和回調(diào)機(jī)制,允許應(yīng)用程序在進(jìn)行I/O操作時(shí)不需要主動(dòng)等待操作完成,而是在操作完成后由操作系統(tǒng)通知應(yīng)用程序。// NIO2

2、Java NIO 通道(Channel)詳解

????????Java NIO通道類似于流,但有一些不同:

可以對(duì)通道進(jìn)行讀和寫操作。流通常是單向的(讀或?qū)?。// 通道可以讀和寫通道可以異步讀寫。通道總是向緩沖區(qū)讀或從緩沖區(qū)寫。

????????如上所述,數(shù)據(jù)總是從通道讀入緩沖區(qū),或者從緩沖區(qū)寫入通道:

????????下面是Java NIO中最重要的Channel實(shí)現(xiàn):

FileChannel:從文件中讀取和寫入數(shù)據(jù)。DatagramChannel:可以通過(guò)UDP在網(wǎng)絡(luò)上讀寫數(shù)據(jù)。SocketChannel:可以通過(guò)TCP在網(wǎng)絡(luò)上讀寫數(shù)據(jù)。ServerSocketChannel:允許偵聽(tīng)傳入的TCP連接,如web服務(wù)器一樣。該Channel會(huì)為每個(gè)傳入連接創(chuàng)建一個(gè)SocketChannel。

如何獲取Channel對(duì)象?

????????在Java NIO中,可以通過(guò)以下幾種方式來(lái)獲取Channel對(duì)象:

????????(1)文件通道(FileChannel):通過(guò)文件輸入流(FileInputStream)或文件輸出流(FileOutputStream)獲取文件通道。示例代碼如下:

FileInputStream fis = new FileInputStream("path/to/file");

FileChannel fileChannel = fis.getChannel();

FileOutputStream fos = new FileOutputStream("path/to/file");

FileChannel fileChannel = fos.getChannel();

????????(2)網(wǎng)絡(luò)通道(SocketChannel、ServerSocketChannel、DatagramChannel):通過(guò)Java NIO提供的網(wǎng)絡(luò)編程類獲取網(wǎng)絡(luò)通道。示例代碼如下:

SocketChannel socketChannel = SocketChannel.open();

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

DatagramChannel datagramChannel = DatagramChannel.open();

????????(3)管道(Pipe):通過(guò)管道獲取通道。管道用于在兩個(gè)線程之間進(jìn)行通信,可以使用Pipe.open()方法打開(kāi)一個(gè)管道,并通過(guò)source()和sink()方法獲取通道。示例代碼如下:

Pipe pipe = Pipe.open();

Pipe.SinkChannel sinkChannel = pipe.sink();

Pipe.SourceChannel sourceChannel = pipe.source();

????????這些方法返回的Channel對(duì)象可以用于讀取或?qū)懭霐?shù)據(jù),執(zhí)行I/O操作。

????????另外,需要注意的是,以上方法獲取的Channel對(duì)象都是阻塞式的。如果想要使用非阻塞模式,可以通過(guò)調(diào)用configureBlocking(false)方法將通道設(shè)置為非阻塞模式,然后可以使用Selector來(lái)進(jìn)行非阻塞I/O操作。//?FileChannel無(wú)此方法,所以它總是阻塞的

SocketChannel socketChannel = SocketChannel.open();

socketChannel.configureBlocking(false);

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.configureBlocking(false);

????????這樣設(shè)置后,可以通過(guò)Selector來(lái)監(jiān)控這些非阻塞通道的狀態(tài),并進(jìn)行相應(yīng)的I/O操作。

????????下面是一個(gè)使用FileChannel復(fù)制文件的基本示例:// 通道+緩沖區(qū)+阻塞式

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.nio.channels.FileChannel;

import java.nio.ByteBuffer;

public class FileCopyExample {

public static void main(String[] args) {

try {

FileInputStream sourceFile = new FileInputStream("F:\\source.txt");

FileOutputStream destinationFile = new FileOutputStream("F:\\destination.txt");

FileChannel sourceChannel = sourceFile.getChannel();

FileChannel destinationChannel = destinationFile.getChannel();

ByteBuffer buffer = ByteBuffer.allocate(1024);

while (sourceChannel.read(buffer) != -1) {

// 翻轉(zhuǎn)緩沖區(qū),為通道寫入序列做準(zhǔn)備:limit設(shè)置為當(dāng)前position的值,position設(shè)置為0,mark設(shè)置為-1(廢棄)

buffer.flip();

destinationChannel.write(buffer);

// 在放置數(shù)據(jù)之前清空緩存,為通道讀寫入序列做準(zhǔn)備:limit設(shè)置為capacity的值,position設(shè)置為0,mark設(shè)置為-1(廢棄):

buffer.clear();

}

sourceChannel.close();

destinationChannel.close();

sourceFile.close();

destinationFile.close();

System.out.println("File copied successfully.");

} catch (Exception e) {

e.printStackTrace();

}

}

}

3、Java NIO 緩沖區(qū)(Buffer)詳解

????????緩沖區(qū)本質(zhì)上是一塊內(nèi)存,你可以向其中寫入數(shù)據(jù),然后可以再次讀取數(shù)據(jù)。該內(nèi)存塊封裝在NIO Buffer對(duì)象中,該對(duì)象提供了一組方法,讓使用內(nèi)存塊變得更容易。

????????Java NIO附帶了以下緩沖區(qū)類型:

ByteBuffer(字節(jié)緩沖區(qū)): ByteBuffer是最常用的緩沖區(qū)類型之一,它可以存儲(chǔ)字節(jié)數(shù)據(jù)。ByteBuffer可以分為直接緩沖區(qū)和非直接緩沖區(qū)。直接緩沖區(qū)使用操作系統(tǒng)的本地內(nèi)存,可以提高I/O操作的性能。CharBuffer(字符緩沖區(qū)): CharBuffer用于存儲(chǔ)字符數(shù)據(jù),它是基于16位Unicode字符的緩沖區(qū)。CharBuffer提供了一些方便的方法來(lái)處理字符數(shù)據(jù)。ShortBuffer、IntBuffer、LongBuffer(短整型、整型和長(zhǎng)整型緩沖區(qū)): 這些緩沖區(qū)類型分別用于存儲(chǔ)短整型、整型和長(zhǎng)整型數(shù)據(jù)。它們提供了特定于數(shù)據(jù)類型的方法和屬性。FloatBuffer、DoubleBuffer(浮點(diǎn)型和雙精度浮點(diǎn)型緩沖區(qū)): 這些緩沖區(qū)類型用于存儲(chǔ)浮點(diǎn)型和雙精度浮點(diǎn)型數(shù)據(jù)。它們也提供了特定于數(shù)據(jù)類型的方法和屬性。

(1)獲取緩沖區(qū)對(duì)象

????????在Java NIO中,可以通過(guò)以下方式來(lái)獲取Buffer對(duì)象:

????????1)使用分配方法(Allocation Methods):每個(gè)緩沖區(qū)類(如ByteBuffer、CharBuffer、IntBuffer等)都提供了allocate()方法來(lái)分配一個(gè)新的緩沖區(qū)對(duì)象。示例代碼如下:

ByteBuffer buffer = ByteBuffer.allocate(1024);

CharBuffer buffer = CharBuffer.allocate(1024);

????????這種方式會(huì)分配一個(gè)指定容量的緩沖區(qū)對(duì)象,可以根據(jù)需要進(jìn)行讀寫操作。

????????2)使用包裝方法(Wrapping Methods):除了分配方法,緩沖區(qū)類還提供了wrap()方法,用于包裝現(xiàn)有的數(shù)組來(lái)創(chuàng)建緩沖區(qū)對(duì)象。示例代碼如下:

byte[] byteArray = new byte[1024];

ByteBuffer buffer = ByteBuffer.wrap(byteArray);

char[] charArray = new char[1024];

CharBuffer buffer = CharBuffer.wrap(charArray);

????????這種方式會(huì)使用現(xiàn)有的數(shù)組作為緩沖區(qū)的數(shù)據(jù)存儲(chǔ)。

????????3)使用視圖方法(View Buffer):緩沖區(qū)類還提供了一些視圖方法,可以創(chuàng)建基于現(xiàn)有緩沖區(qū)的新緩沖區(qū)對(duì)象。這些視圖緩沖區(qū)共享底層緩沖區(qū)的數(shù)據(jù),并根據(jù)視圖的不同提供不同類型的訪問(wèn)方式。常見(jiàn)的視圖緩沖區(qū)有ByteBuffer的子類:CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer和DoubleBuffer等。示例代碼如下:

ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

CharBuffer charBuffer = byteBuffer.asCharBuffer();

IntBuffer intBuffer = byteBuffer.asIntBuffer();

????????這種方式可以根據(jù)需要?jiǎng)?chuàng)建適合特定數(shù)據(jù)類型的緩沖區(qū)。

(2)將數(shù)據(jù)寫入Buffer以及從Buffer讀取數(shù)據(jù)

????????1)將數(shù)據(jù)寫入Buffer

????????在Java NIO中,有多種方式可以將數(shù)據(jù)寫入Buffer對(duì)象。以下是幾種常用的方式:

????????使用put()方法:Buffer類提供了多個(gè)put()方法的重載形式,可以根據(jù)不同數(shù)據(jù)類型來(lái)寫入數(shù)據(jù)。例如:

put(byte b):將單個(gè)字節(jié)寫入Buffer。put(byte[] array):將字節(jié)數(shù)組的內(nèi)容寫入Buffer。put(ByteBuffer src):將另一個(gè)ByteBuffer的內(nèi)容寫入當(dāng)前Buffer。putXxx()方法(如putChar(char value)、putInt(int value)等):根據(jù)數(shù)據(jù)類型將特定類型的數(shù)據(jù)寫入Buffer。

? ? ? ? 使用通道(Channel):可以使用通道的read()方法將數(shù)據(jù)直接寫入Buffer。例如:

int bytesRead = inChannel.read(buf); //read into buffer.

????????2)從Buffer讀取數(shù)據(jù)

????????從Buffer讀取數(shù)據(jù)的兩種方式:使用get()方法和通道(Channel)

????????使用get()方法從緩沖區(qū)讀取數(shù)據(jù):

byte aByte = buf.get();

????????還有許多其他版本的get()方法,允許以許多不同的方式從Buffer讀取數(shù)據(jù)。例如,在特定位置讀取,或者從緩沖區(qū)讀取一個(gè)字節(jié)數(shù)組的數(shù)據(jù)等。

????????將數(shù)據(jù)從緩沖區(qū)讀入通道(Channel):可以使用通道的write()方法將數(shù)據(jù)從Buffer讀入通道。例如:

//read from buffer into channel.

int bytesWritten = inChannel.write(buf);

(3)通道Channel和緩沖區(qū)Buffer的相互讀寫

????????1)把數(shù)據(jù)從Channel寫入Buffer:channel.read(buffer)

// 創(chuàng)建一個(gè)FileChannel

FileChannel channel = new FileInputStream("path/to/file").getChannel();

// 創(chuàng)建一個(gè)ByteBuffer

ByteBuffer buffer = ByteBuffer.allocate(1024);

// 將數(shù)據(jù)從Channel讀取到Buffer中

int bytesRead = channel.read(buffer);

????????2)把數(shù)據(jù)從Buffer寫入Channel:channel.write(buffer)

// 創(chuàng)建一個(gè)FileChannel

FileChannel channel = new FileOutputStream("path/to/file").getChannel();

// 創(chuàng)建一個(gè)ByteBuffer

ByteBuffer buffer = ByteBuffer.allocate(1024);

// 將數(shù)據(jù)寫入Buffer

buffer.put("Hello, World!".getBytes());

buffer.flip(); // 切換到讀模式

// 將Buffer中的數(shù)據(jù)寫入Channel

while (buffer.hasRemaining()) {

channel.write(buffer);

}

// 關(guān)閉Channel

channel.close();

(4)圖解緩沖區(qū)Buffer

????????1)緩沖區(qū)的創(chuàng)建

創(chuàng)建一個(gè)緩沖區(qū):ByteBuffer allocate = ByteBuffer.allocate(10);然后往緩沖區(qū)中添加數(shù)據(jù):allocate.put("ABC".getBytes());

容量(Capacity):Buffer的容量表示它所能夠存儲(chǔ)的數(shù)據(jù)元素的最大數(shù)量。一旦Buffer被分配,它的容量就是固定的,不能更改。//內(nèi)存塊位置(Position):Buffer的位置表示下一個(gè)要讀取或?qū)懭氲脑氐乃饕?。初始時(shí),位置為0,并且在讀取或?qū)懭氩僮骱髸?huì)自動(dòng)更新。限制(Limit):Buffer的限制表示在讀取或?qū)懭氩僮髦?,最多可以讀取或?qū)懭氲脑財(cái)?shù)量。初始時(shí),限制與容量相等(limit = capacity),并且在讀取或?qū)懭氩僮骱罂梢允謩?dòng)設(shè)置。

????????示例代碼:

public static void main(String[] args) throws IOException {

//1、創(chuàng)建一個(gè)緩沖區(qū)

ByteBuffer allocate = ByteBuffer.allocate(10);

System.out.println(allocate.position());//0 獲取當(dāng)前索引所在位置

System.out.println(allocate.limit());//10 最多能操作到哪個(gè)索引位置

System.out.println(allocate.capacity());//10 返回緩沖區(qū)總長(zhǎng)度

System.out.println(allocate.remaining());//10 還有多少個(gè)可以操作的個(gè)數(shù):limit - position

System.out.println("———————————————————");

//2、添加3個(gè)字節(jié)

allocate.put("ABC".getBytes());

System.out.println(allocate.position());//3 獲取當(dāng)前索引所在位置

System.out.println(allocate.limit());//10 最多能操作到哪個(gè)索引位置

System.out.println(allocate.capacity());//10 返回緩沖區(qū)總長(zhǎng)度

System.out.println(allocate.remaining());//7 還有多少個(gè)可以操作的個(gè)數(shù)

}

????????2)緩沖區(qū)的讀寫模式轉(zhuǎn)換

????????寫模式切換到讀模式:flip()方法

????????flip()方法:用于將Buffer從寫模式切換到讀模式。flip()方法的作用是設(shè)置limit為當(dāng)前位置,然后將position重置為0,以便在讀取數(shù)據(jù)之前將Buffer準(zhǔn)備好。

????????當(dāng)將數(shù)據(jù)寫入Buffer時(shí),Buffer會(huì)跟蹤寫入了多少數(shù)據(jù)。一旦需要從Buffer讀取數(shù)據(jù),就需要調(diào)用flip()方法將Buffer從寫入模式切換到讀取模式。在讀取模式下,緩沖區(qū)允許讀取寫入緩沖區(qū)的所有數(shù)據(jù)。// 作用:將緩沖區(qū)從寫入模式切換到讀取模式,讀取緩沖區(qū)中的數(shù)據(jù)

????????讀模式切換到寫模式:clear()和compact()方法

????????clear()和compact():一旦從Buffer中讀取了所有數(shù)據(jù),就需要清除緩沖區(qū),以便為再次寫入做好準(zhǔn)備。調(diào)用clear()或compact()方法可以達(dá)到清除緩沖區(qū)的效果。clear()方法會(huì)清除整個(gè)緩沖區(qū),而compact()方法只會(huì)清除已經(jīng)讀取的數(shù)據(jù),未讀的數(shù)據(jù)都會(huì)被移到緩沖區(qū)的開(kāi)頭,新進(jìn)的數(shù)據(jù)將在未讀數(shù)據(jù)之后進(jìn)行寫入。//?clear()和compact() 并不會(huì)真正清除數(shù)據(jù),只是修改了相關(guān)位置數(shù)據(jù)的指針

????????切換到寫模式:clear()方法將Buffer從讀模式切換到寫模式,重置位置position為0,限制limit設(shè)置為容量capacity,以便重新寫入數(shù)據(jù)。

????????清空數(shù)據(jù):clear()方法不會(huì)清除緩沖區(qū)的數(shù)據(jù),而是將緩沖區(qū)標(biāo)記為可重寫狀態(tài),之前寫入的數(shù)據(jù)仍然存在,但是在寫模式下可以覆蓋它們。

4、Java NIO 選擇器/Selector詳解

????????在Java NIO中,Selector是一個(gè)可用于多路復(fù)用(Multiplexing)I/O操作的關(guān)鍵組件。它允許單個(gè)線程同時(shí)管理多個(gè)通道(Channels),監(jiān)控這些通道的I/O事件(如讀就緒、寫就緒等),并且在有就緒事件發(fā)生時(shí)進(jìn)行響應(yīng)。

????????以下是關(guān)于Selector的一些重要概念和使用方式:

????????1)創(chuàng)建和獲取Selector對(duì)象:

創(chuàng)建:可以通過(guò)Selector.open()方法創(chuàng)建一個(gè)新的Selector對(duì)象。獲?。嚎梢酝ㄟ^(guò)SelectableChannel對(duì)象的selector()方法獲取與之關(guān)聯(lián)的Selector對(duì)象。

? ? ? ? 2)注冊(cè)通道到Selector:

通道必須是可選擇的(SelectableChannel),如SocketChannel、ServerSocketChannel、DatagramChannel等。通過(guò)調(diào)用通道的register(Selector selector, int interestOps)方法將通道注冊(cè)到Selector上。interestOps參數(shù)表示感興趣的事件類型,可使用SelectionKey類的常量來(lái)指定,如SelectionKey.OP_READ、SelectionKey.OP_WRITE等。

????????3)選擇操作:

調(diào)用Selector的select()方法進(jìn)行選擇操作,它會(huì)阻塞直到至少有一個(gè)注冊(cè)的通道就緒或被中斷。返回值表示有多少個(gè)通道已經(jīng)就緒,可以通過(guò)Selector的selectedKeys()方法獲取就緒的SelectionKey集合。

????????4)處理就緒事件:

通過(guò)遍歷selectedKeys()方法返回的就緒SelectionKey集合,可以獲取每個(gè)就緒通道的相關(guān)信息??梢允褂肧electionKey的channel()方法獲取就緒通道,readyOps()方法獲取就緒的操作類型??梢愿鶕?jù)就緒的操作類型執(zhí)行相應(yīng)的處理邏輯。

????????5)取消注冊(cè)和關(guān)閉:

調(diào)用SelectionKey的cancel()方法可以取消通道的注冊(cè)。調(diào)用Selector的close()方法可以關(guān)閉Selector對(duì)象。

????????使用Selector可以在單個(gè)線程中同時(shí)處理多個(gè)通道的I/O操作,避免了為每個(gè)通道分配一個(gè)線程的開(kāi)銷。這種方式常用于需要同時(shí)管理多個(gè)通道的高并發(fā)應(yīng)用場(chǎng)景,如網(wǎng)絡(luò)服務(wù)器。

????????需要注意的是,Selector是基于事件驅(qū)動(dòng)的,所以在處理就緒事件時(shí)需要注意及時(shí)響應(yīng)并處理,否則可能會(huì)錯(cuò)過(guò)事件。

(1)將channel注冊(cè)到Selector上

????????使用Selector,必須首先向選擇器注冊(cè)通道。向選擇器注冊(cè)通道可以使用Channel.register()方法進(jìn)行注冊(cè),如下所示:

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

????????注意:Channel必須處于非阻塞模式才能與Selector一起使用。所以不能在選擇器中使用FileChannel,因?yàn)镕ileChannel不能切換到非阻塞模式。// 所以一般文件的讀寫還是使用的BIO

????????向Selector注冊(cè)channel,也是由操作系統(tǒng)完成的,也調(diào)用了底層操作系統(tǒng)的相關(guān)實(shí)現(xiàn),這是因?yàn)镾elector的實(shí)現(xiàn)機(jī)制基于操作系統(tǒng)提供的底層I/O多路復(fù)用機(jī)制實(shí)現(xiàn)的。

? ? ? ? 通過(guò)register()方法注冊(cè)channel時(shí),需要指定偵聽(tīng)的 I/O 事件類型(讀、寫等)。選擇器可以偵聽(tīng)四種不同的事件:Connect、Accept、Read、Write,具體如下所示:

????????SelectionKey中定義的4種事件: // Java NIO基于事件驅(qū)動(dòng)

SelectionKey.OP_ACCEPT:接收連接繼續(xù)事件,表示服務(wù)器監(jiān)聽(tīng)到了客戶連接,服務(wù)器可以接收這個(gè)連接了SelectionKey.OP_CONNECT:連接就緒事件,表示客戶端與服務(wù)器的連接已經(jīng)建立成功SelectionKey.OP_READ:讀就緒事件,表示通道中已經(jīng)有了可讀的數(shù)據(jù),可以執(zhí)行讀操作了(通道目前有數(shù)據(jù),可以進(jìn)行讀操作了)SelectionKey.OP_WRITE:寫就緒事件,表示已經(jīng)可以向通道寫數(shù)據(jù)了(通道目前可以用于寫操作)

(2)為什么說(shuō) select() 方法是阻塞的?

????????select()方法是阻塞的主要原因是它的實(shí)現(xiàn)機(jī)制基于操作系統(tǒng)提供的底層I/O多路復(fù)用機(jī)制,例如Linux上的select()系統(tǒng)調(diào)用或Windows上的select()函數(shù)。這些底層機(jī)制都是阻塞的,因此select()方法在調(diào)用底層機(jī)制時(shí)也會(huì)阻塞。//?select()方法是由操作系統(tǒng)實(shí)現(xiàn)的

? ? ? ? 看下源碼,點(diǎn)擊Selector.select()方法,進(jìn)入Selector的實(shí)現(xiàn)類SelectorImpl,它會(huì)調(diào)用一個(gè)lockAndDoSelect()方法:// 基于 java 8

public int select(long var1) throws IOException {

if (var1 < 0L) {

throw new IllegalArgumentException("Negative timeout");

} else {

return this.lockAndDoSelect(var1 == 0L ? -1L : var1);

}

}

????????進(jìn)入lockAndDoSelect()方法后,在該方法中會(huì)調(diào)用SelectorImpl.doSelect()方法。

protected abstract int doSelect(long var1) throws IOException;

private int lockAndDoSelect(long var1) throws IOException {

synchronized(this) {

if (!this.isOpen()) {

throw new ClosedSelectorException();

} else {

int var10000;

synchronized(this.publicKeys) {

synchronized(this.publicSelectedKeys) {

var10000 = this.doSelect(var1);

}

}

return var10000;

}

}

}

????????SelectorImpl.doSelect()方法是一個(gè)抽象方法,既然是抽象方法就會(huì)有對(duì)應(yīng)的實(shí)現(xiàn)。繼續(xù)跟進(jìn)到該方法的實(shí)現(xiàn)類WindowsSelectorImpl.doSelect()方法中,其中有一行代碼:this.subSelector.poll(),這行代碼就是去調(diào)用操作系統(tǒng)的底層實(shí)現(xiàn)機(jī)制了。

protected int doSelect(long var1) throws IOException {

if (this.channelArray == null) {

throw new ClosedSelectorException();

} else {

// 省略...

if (this.interruptTriggered) {

this.resetWakeupSocket();

return 0;

} else {

// 省略...

try {

this.begin();

try {

// 在這里調(diào)用系統(tǒng)的select()方法

this.subSelector.poll();

} catch (IOException var7) {

this.finishLock.setException(var7);

}

if (this.threads.size() > 0) {

this.finishLock.waitForHelperThreads();

}

} finally {

this.end();

}

// 省略...

}

}

}

????????WindowsSelectorImpl是Java NIO庫(kù)中用于Windows平臺(tái)上的Selector的具體實(shí)現(xiàn)類。它實(shí)現(xiàn)了Selector接口,并提供了在Windows操作系統(tǒng)上使用I/O多路復(fù)用機(jī)制的功能。??

? ? ? 繼續(xù)跟進(jìn)this.subSelector.poll()方法,可以看到它調(diào)用了一個(gè)本地方法:// 該方法阻塞

private int poll() throws IOException {

return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress, Math.min(WindowsSelectorImpl.this.totalChannels, 1024), this.readFds, this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);

}

// 本地方法

private native int poll0(long var1, int var3, int[] var4, int[] var5, int[] var6, long var7);

????????this.subSelector對(duì)象

private final WindowsSelectorImpl.SubSelector subSelector = new WindowsSelectorImpl.SubSelector();

????????在WindowsSelectorImpl類中,SubSelector是一個(gè)內(nèi)部類,用于管理特定事件類型的I/O事件。SubSelector繼承自AbstractPollArrayWrapper類,用于封裝I/O事件的輪詢和處理。

????????SubSelector類中的poll()方法是用于執(zhí)行對(duì)已注冊(cè)通道的輪詢操作,并返回就緒的通道數(shù)量。

(3)Selector 的 selectedKeys() 方法

????????一旦調(diào)用了select()方法,并且它的返回值表明一個(gè)或多個(gè)通道已經(jīng)準(zhǔn)備就緒,接下來(lái)就可以調(diào)用選擇器的selectedKeys()方法,獲取已經(jīng)準(zhǔn)備就緒的通道集。:// 這個(gè)步驟還挺難理解的,先執(zhí)行select()方法,然后再執(zhí)行selectedKeys()方法

????????在Java NIO中,Selector的selectedKeys()方法用于獲取當(dāng)前已選擇鍵集合(selected key set)。

public Set selectedKeys() {

if (!this.isOpen() && !Util.atBugLevel("1.4")) {

throw new ClosedSelectorException();

} else {

return this.publicSelectedKeys;

}

}

????????該方法返回一個(gè)Set對(duì)象,表示當(dāng)前已選擇的鍵集合。這個(gè)集合包含了在上一次選擇操作中就緒的SelectionKey對(duì)象??梢酝ㄟ^(guò)遍歷該集合來(lái)處理相應(yīng)的就緒事件。

????????需要注意的是,selectedKeys()方法返回的是一個(gè)可變的集合,即可以直接在返回的集合上進(jìn)行增刪操作。這意味著在處理完就緒事件后,需要手動(dòng)從集合中移除相應(yīng)的SelectionKey,以便下次的選擇操作能正確更新就緒狀態(tài)。// 先select(),后執(zhí)行selectedKeys()

????????在使用selectedKeys()方法時(shí),一般會(huì)結(jié)合Iterator來(lái)遍歷集合,并逐個(gè)處理就緒的SelectionKey。示例如下:

Set selectedKeys = selector.selectedKeys();

Iterator keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {

SelectionKey key = keyIterator.next();

if(key.isAcceptable()) {

// a connection was accepted by a ServerSocketChannel.

} else if (key.isConnectable()) {

// a connection was established with a remote server.

} else if (key.isReadable()) {

// a channel is ready for reading

} else if (key.isWritable()) {

// a channel is ready for writing

}

keyIterator.remove();

}

????????在上述該循環(huán)中,會(huì)循環(huán)判斷每一個(gè)SelectionKey引用的通道是否已經(jīng)準(zhǔn)備就緒。

????????在每次迭代結(jié)束時(shí)都會(huì)調(diào)用keyIterator.remove(),這是因?yàn)檫x擇器自己不會(huì)從鍵集中刪除SelectionKey的實(shí)例。所以,當(dāng)你完成對(duì)該通道的處理時(shí),就需要手動(dòng)去移除它。當(dāng)通道再次準(zhǔn)備就緒時(shí),選擇器會(huì)再次將這個(gè)鍵添加到所選的鍵集中。// 手動(dòng)移除鍵

????????另外,在處理具體的通道時(shí),SelectionKey.channel()方法返回的通道應(yīng)該被強(qiáng)制轉(zhuǎn)換為需要使用的通道類型,例如ServerSocketChannel或SocketChannel等。

SocketChannel sc = (SocketChannel) key.channel();

5、為什么說(shuō)Java NIO是事件驅(qū)動(dòng)的?

????????Java NIO(New I/O)是事件驅(qū)動(dòng)的,主要基于以下兩個(gè)核心組件:選擇器(Selector)和選擇鍵(SelectionKey)。

選擇器(Selector):選擇器是Java NIO中的核心組件之一。它允許單線程管理多個(gè)通道(如SocketChannel、ServerSocketChannel等),并根據(jù)通道的就緒狀態(tài)進(jìn)行事件驅(qū)動(dòng)的操作。選擇器可以在通道上注冊(cè)想要偵聽(tīng)的I/O事件(如讀、寫等),并在事件就緒時(shí)通知應(yīng)用程序。選擇鍵(SelectionKey):選擇鍵是通道在選擇器上的注冊(cè)信息。當(dāng)通道注冊(cè)到選擇器上時(shí),將創(chuàng)建一個(gè)選擇鍵來(lái)表示該注冊(cè)關(guān)系。選擇鍵包含了通道、選擇器以及想要偵聽(tīng)的I/O事件類型。通過(guò)選擇鍵,可以獲取和修改想要偵聽(tīng)的事件類型,并獲取就緒狀態(tài)的通道。

????????Java NIO的事件驅(qū)動(dòng)模型基于以下原理:

注冊(cè):應(yīng)用程序?qū)⑼ǖ雷?cè)到選擇器上,并指定想要偵聽(tīng)的I/O事件類型(如讀、寫等)。選擇:選擇器通過(guò)調(diào)用select()方法進(jìn)行阻塞,等待就緒的通道。當(dāng)有通道就緒時(shí),select()方法會(huì)返回就緒通道的數(shù)量,并可以通過(guò)selectedKeys()方法獲取到就緒通道的選擇鍵集合。就緒通知:選擇器在某個(gè)通道就緒時(shí),會(huì)將相應(yīng)的選擇鍵添加到選擇鍵集合中。應(yīng)用程序可以通過(guò)遍歷選擇鍵集合來(lái)獲取就緒的通道和感興趣的事件類型。事件處理:應(yīng)用程序可以根據(jù)就緒的通道和感興趣的事件類型,執(zhí)行相應(yīng)的操作,如讀取數(shù)據(jù)、寫入數(shù)據(jù)、接受連接等。處理完事件后,可以取消或修改通道的注冊(cè)信息。

????????Java NIO的事件驅(qū)動(dòng)模型允許應(yīng)用程序在單線程中同時(shí)處理多個(gè)通道的I/O操作,提高了系統(tǒng)的并發(fā)性和可擴(kuò)展性。相比于傳統(tǒng)的阻塞I/O模型,事件驅(qū)動(dòng)模型減少了線程的創(chuàng)建和管理開(kāi)銷,提高了系統(tǒng)的效率和響應(yīng)能力。

// 在Java NIO中,Selector并不會(huì)不斷輪詢注冊(cè)在其上的所有通道。實(shí)際上,Selector使用的是操作系統(tǒng)提供的事件通知機(jī)制,如epoll、kqueue等,來(lái)監(jiān)聽(tīng)和等待就緒的通道。

// 驗(yàn)證操作系統(tǒng)提供的事件通知機(jī)制,待續(xù)...

6、NIO編程完整的代碼示例

????????客戶端代碼:// 以下代碼可直接運(yùn)行

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.SocketChannel;

import java.util.Iterator;

import java.util.Set;

import java.util.UUID;

public class NioClient {

public static void main(String[] args) {

String host = "127.0.0.1";

int port = 8001;

Selector selector = null;

SocketChannel socketChannel = null;

try {

selector = Selector.open();

socketChannel = SocketChannel.open();

socketChannel.configureBlocking(false); // 非阻塞

// 如果直接連接成功,則注冊(cè)到多路復(fù)用器上,發(fā)送請(qǐng)求消息,讀應(yīng)答

if (socketChannel.connect(new InetSocketAddress(host, port))) {

socketChannel.register(selector, SelectionKey.OP_READ);

doWrite(socketChannel);

} else {

socketChannel.register(selector, SelectionKey.OP_CONNECT);

}

} catch (IOException e) {

e.printStackTrace();

System.exit(1);

}

while (true) {

try {

selector.select(1000);

Set selectedKeys = selector.selectedKeys();

Iterator it = selectedKeys.iterator();

SelectionKey key = null;

while (it.hasNext()) {

key = it.next();

it.remove();

try {

//處理每一個(gè)channel

handleInput(selector, key);

} catch (Exception e) {

if (key != null) {

key.cancel();

if (key.channel() != null) {

key.channel().close();

}

}

}

}

} catch (Exception e) {

e.printStackTrace();

}

}

// 多路復(fù)用器關(guān)閉后,所有注冊(cè)在上面的Channel資源都會(huì)被自動(dòng)去注冊(cè)并關(guān)閉

// if (selector != null)

// try {

// selector.close();

// } catch (IOException e) {

// e.printStackTrace();

// }

//

// }

}

public static void doWrite(SocketChannel sc) throws IOException {

byte[] str = UUID.randomUUID().toString().getBytes();

ByteBuffer writeBuffer = ByteBuffer.allocate(str.length);

writeBuffer.put(str);

writeBuffer.flip();

sc.write(writeBuffer);

}

public static void handleInput(Selector selector, SelectionKey key) throws Exception {

if (key.isValid()) {

// 判斷是否連接成功

SocketChannel sc = (SocketChannel) key.channel();

if (key.isConnectable()) {

if (sc.finishConnect()) {

sc.register(selector, SelectionKey.OP_READ);

}

}

if (key.isReadable()) {

ByteBuffer readBuffer = ByteBuffer.allocate(1024);

int readBytes = sc.read(readBuffer);

if (readBytes > 0) {

readBuffer.flip();

byte[] bytes = new byte[readBuffer.remaining()];

readBuffer.get(bytes);

String body = new String(bytes, "UTF-8");

System.out.println("Server said : " + body);

} else if (readBytes < 0) {

// 對(duì)端鏈路關(guān)閉

key.cancel();

sc.close();

} else {

; // 讀到0字節(jié),忽略

}

}

Thread.sleep(3000);

doWrite(sc);

}

}

}

????????服務(wù)端代碼:

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 NioServer {

public static void main(String[] args) throws IOException {

int port = 8001;

Selector selector = null;

ServerSocketChannel servChannel = null;

try {

selector = Selector.open();

servChannel = ServerSocketChannel.open();

servChannel.configureBlocking(false);

servChannel.socket().bind(new InetSocketAddress(port), 1024);

servChannel.register(selector, SelectionKey.OP_ACCEPT);

System.out.println("服務(wù)器在8001端口守候");

} catch (IOException e) {

e.printStackTrace();

System.exit(1);

}

while (true) {

try {

selector.select(1000);

Set selectedKeys = selector.selectedKeys();

Iterator it = selectedKeys.iterator();

SelectionKey key = null;

while (it.hasNext()) {

key = it.next();

it.remove();

try {

handleInput(selector, key);

} catch (Exception e) {

if (key != null) {

key.cancel();

if (key.channel() != null) {

key.channel().close();

}

}

}

}

} catch (Exception ex) {

ex.printStackTrace();

}

try {

Thread.sleep(500);

} catch (Exception ex) {

ex.printStackTrace();

}

}

}

public static void handleInput(Selector selector, SelectionKey key) throws IOException {

if (key.isValid()) {

// 處理新接入的請(qǐng)求消息

if (key.isAcceptable()) {

// Accept the new connection

ServerSocketChannel ssc = (ServerSocketChannel) key.channel();

SocketChannel sc = ssc.accept();

sc.configureBlocking(false);

// Add the new connection to the selector

sc.register(selector, SelectionKey.OP_READ);

}

if (key.isReadable()) {

// Read the data

SocketChannel sc = (SocketChannel) key.channel();

ByteBuffer readBuffer = ByteBuffer.allocate(1024);

int readBytes = sc.read(readBuffer);

if (readBytes > 0) {

readBuffer.flip();

byte[] bytes = new byte[readBuffer.remaining()];

readBuffer.get(bytes);

String request = new String(bytes, "UTF-8"); //接收到的輸入

System.out.println("client said: " + request);

String response = request + " 666";

doWrite(sc, response);

} else if (readBytes < 0) {

// 對(duì)端鏈路關(guān)閉

key.cancel();

sc.close();

} else {

; // 讀到0字節(jié),忽略

}

}

}

}

public static void doWrite(SocketChannel channel, String response) throws IOException {

if (response != null && response.trim().length() > 0) {

byte[] bytes = response.getBytes();

ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);

writeBuffer.put(bytes);

writeBuffer.flip();

channel.write(writeBuffer);

}

}

}

????????至此,全文結(jié)束。

????????附NIO編程的一些學(xué)習(xí)資料《Java NIO 教程》

柚子快報(bào)激活碼778899分享:java NIO編程

http://yzkb.51969.com/

相關(guān)鏈接

評(píng)論可見(jiàn),查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。

轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://gantiao.com.cn/post/18942911.html

發(fā)布評(píng)論

您暫未設(shè)置收款碼

請(qǐng)?jiān)谥黝}配置——文章設(shè)置里上傳

掃描二維碼手機(jī)訪問(wèn)

文章目錄